Graceful exit when no states to run (#815)
* rm unnecessary return, change assert to graceful handle * Add regression test * Add regression test * Correct logic * Rm unnecessary check This branch is unnecessary, if this condition is true, then ultimately the executor will do nothing, and we'll simply return anyway. * Raise NoAliveStates if no alive states to execute * docstr update * Update mult_tx_analysis to handle NoAliveStates * Fancy * Update test * Codeclimate fmt * fmt * better readability * add docstr
This commit is contained in:
parent
a21c8b6012
commit
fb79127bc2
@ -1,6 +1,7 @@
|
|||||||
import string
|
import string
|
||||||
|
|
||||||
from . import Manticore
|
from . import Manticore
|
||||||
|
from .manticore import ManticoreError
|
||||||
from .core.smtlib import ConstraintSet, Operators, solver, issymbolic, Array, Expression, Constant, operators
|
from .core.smtlib import ConstraintSet, Operators, solver, issymbolic, Array, Expression, Constant, operators
|
||||||
from .core.smtlib.visitors import arithmetic_simplifier
|
from .core.smtlib.visitors import arithmetic_simplifier
|
||||||
from .platforms import evm
|
from .platforms import evm
|
||||||
@ -22,6 +23,14 @@ logger = logging.getLogger(__name__)
|
|||||||
################ Detectors ####################
|
################ Detectors ####################
|
||||||
|
|
||||||
|
|
||||||
|
class EthereumError(ManticoreError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NoAliveStates(EthereumError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Detector(Plugin):
|
class Detector(Plugin):
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@ -872,7 +881,7 @@ class ManticoreEVM(Manticore):
|
|||||||
return address
|
return address
|
||||||
|
|
||||||
def transaction(self, caller, address, value, data):
|
def transaction(self, caller, address, value, data):
|
||||||
''' Issue a transaction
|
''' Issue a symbolic transaction
|
||||||
|
|
||||||
:param caller: the address of the account sending the transaction
|
:param caller: the address of the account sending the transaction
|
||||||
:type caller: int or EVMAccount
|
:type caller: int or EVMAccount
|
||||||
@ -882,12 +891,18 @@ class ManticoreEVM(Manticore):
|
|||||||
:type value: int or SValue
|
:type value: int or SValue
|
||||||
:param data: initial data
|
:param data: initial data
|
||||||
:return: an EVMAccount
|
:return: an EVMAccount
|
||||||
|
:raises NoAliveStates: if there are no alive states to execute
|
||||||
'''
|
'''
|
||||||
if isinstance(address, EVMAccount):
|
if isinstance(address, EVMAccount):
|
||||||
address = int(address)
|
address = int(address)
|
||||||
if isinstance(caller, EVMAccount):
|
if isinstance(caller, EVMAccount):
|
||||||
caller = int(caller)
|
caller = int(caller)
|
||||||
|
|
||||||
|
with self.locked_context('seth') as context:
|
||||||
|
has_alive_states = context['_saved_states'] or self.initial_state is not None
|
||||||
|
if not has_alive_states:
|
||||||
|
raise NoAliveStates
|
||||||
|
|
||||||
if isinstance(data, self.SByte):
|
if isinstance(data, self.SByte):
|
||||||
data = (None,) * data.size
|
data = (None,) * data.size
|
||||||
with self.locked_context('seth') as context:
|
with self.locked_context('seth') as context:
|
||||||
@ -925,7 +940,10 @@ class ManticoreEVM(Manticore):
|
|||||||
current_coverage = 0
|
current_coverage = 0
|
||||||
|
|
||||||
while current_coverage < 100:
|
while current_coverage < 100:
|
||||||
run_symbolic_tx()
|
try:
|
||||||
|
run_symbolic_tx()
|
||||||
|
except NoAliveStates:
|
||||||
|
break
|
||||||
|
|
||||||
if tx_limit is not None:
|
if tx_limit is not None:
|
||||||
tx_limit -= 1
|
tx_limit -= 1
|
||||||
@ -947,8 +965,7 @@ class ManticoreEVM(Manticore):
|
|||||||
# Check if there is a pending transaction
|
# Check if there is a pending transaction
|
||||||
with self.locked_context('seth') as context:
|
with self.locked_context('seth') as context:
|
||||||
assert context['_pending_transaction'] is not None
|
assert context['_pending_transaction'] is not None
|
||||||
# there is at least one states in seth saved states
|
|
||||||
assert context['_saved_states'] or self.initial_state is not None
|
|
||||||
# there is no states added to the executor queue
|
# there is no states added to the executor queue
|
||||||
assert len(self._executor.list()) == 0
|
assert len(self._executor.list()) == 0
|
||||||
|
|
||||||
@ -958,7 +975,7 @@ class ManticoreEVM(Manticore):
|
|||||||
|
|
||||||
# A callback will use _pending_transaction and issue the transaction
|
# A callback will use _pending_transaction and issue the transaction
|
||||||
# in each state (see load_state_callback)
|
# in each state (see load_state_callback)
|
||||||
result = super(ManticoreEVM, self).run(**kwargs)
|
super(ManticoreEVM, self).run(**kwargs)
|
||||||
|
|
||||||
with self.locked_context('seth') as context:
|
with self.locked_context('seth') as context:
|
||||||
if len(context['_saved_states']) == 1:
|
if len(context['_saved_states']) == 1:
|
||||||
@ -968,7 +985,6 @@ class ManticoreEVM(Manticore):
|
|||||||
|
|
||||||
# clear pending transcations. We are done.
|
# clear pending transcations. We are done.
|
||||||
context['_pending_transaction'] = None
|
context['_pending_transaction'] = None
|
||||||
return result
|
|
||||||
|
|
||||||
def save(self, state, final=False):
|
def save(self, state, final=False):
|
||||||
''' Save a state in secondary storage and add it to running or final lists
|
''' Save a state in secondary storage and add it to running or final lists
|
||||||
|
|||||||
@ -33,6 +33,13 @@ logger = logging.getLogger(__name__)
|
|||||||
log.init_logging()
|
log.init_logging()
|
||||||
|
|
||||||
|
|
||||||
|
class ManticoreError(Exception):
|
||||||
|
"""
|
||||||
|
Top level Exception object for custom exception hierarchy
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def make_binja(program, disasm, argv, env, symbolic_files, concrete_start=''):
|
def make_binja(program, disasm, argv, env, symbolic_files, concrete_start=''):
|
||||||
def _check_disassembler_present(disasm):
|
def _check_disassembler_present(disasm):
|
||||||
if is_binja_disassembler(disasm):
|
if is_binja_disassembler(disasm):
|
||||||
|
|||||||
7
tests/binaries/795.sol
Normal file
7
tests/binaries/795.sol
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
contract Simple {
|
||||||
|
function f(uint a) payable public {
|
||||||
|
if (a == 65) {
|
||||||
|
revert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -128,7 +128,7 @@ class IntegrationTest(unittest.TestCase):
|
|||||||
self.assertTrue(len(actual) > 100 )
|
self.assertTrue(len(actual) > 100 )
|
||||||
|
|
||||||
def test_eth_regressions(self):
|
def test_eth_regressions(self):
|
||||||
contracts = [676, 678, 701, 714, 735, 760, 780]
|
contracts = [676, 678, 701, 714, 735, 760, 780, 795]
|
||||||
for contract in contracts:
|
for contract in contracts:
|
||||||
self._simple_cli_run('{}.sol'.format(contract))
|
self._simple_cli_run('{}.sol'.format(contract))
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import os
|
|||||||
from manticore.core.smtlib import ConstraintSet, operators
|
from manticore.core.smtlib import ConstraintSet, operators
|
||||||
from manticore.core.smtlib.expression import BitVec
|
from manticore.core.smtlib.expression import BitVec
|
||||||
from manticore.core.state import State
|
from manticore.core.state import State
|
||||||
from manticore.ethereum import ManticoreEVM, IntegerOverflow, Detector
|
from manticore.ethereum import ManticoreEVM, IntegerOverflow, Detector, NoAliveStates
|
||||||
from manticore.platforms.evm import EVMWorld, ConcretizeStack, concretized_args
|
from manticore.platforms.evm import EVMWorld, ConcretizeStack, concretized_args
|
||||||
|
|
||||||
|
|
||||||
@ -92,6 +92,31 @@ class EthTests(unittest.TestCase):
|
|||||||
self.assertIn('STOP', context)
|
self.assertIn('STOP', context)
|
||||||
self.assertIn('REVERT', context)
|
self.assertIn('REVERT', context)
|
||||||
|
|
||||||
|
def test_graceful_handle_no_alive_states(self):
|
||||||
|
"""
|
||||||
|
If there are no alive states, or no initial states, we should not crash. issue #795
|
||||||
|
"""
|
||||||
|
# initiate the blockchain
|
||||||
|
m = ManticoreEVM()
|
||||||
|
source_code = '''
|
||||||
|
contract Simple {
|
||||||
|
function f(uint a) payable public {
|
||||||
|
if (a == 65) {
|
||||||
|
revert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Initiate the accounts
|
||||||
|
user_account = m.create_account(balance=1000)
|
||||||
|
contract_account = m.solidity_create_contract(source_code, owner=user_account, balance=0)
|
||||||
|
|
||||||
|
contract_account.f(1) # it works
|
||||||
|
contract_account.f(65) # it works
|
||||||
|
with self.assertRaises(NoAliveStates):
|
||||||
|
contract_account.f(m.SValue) # no alive states, but try to run a tx anyway
|
||||||
|
|
||||||
def test_can_create(self):
|
def test_can_create(self):
|
||||||
mevm = ManticoreEVM()
|
mevm = ManticoreEVM()
|
||||||
source_code = """
|
source_code = """
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user