Implement state.generate_testcase (#438)
* ran * Some dirty work * Rm unnecessary sanitization * Fix state.generate_inputs event publishing We don't need to manually pass `self` because State is automatically forwarded as an argument. Also correctly specify a message for the state and expose to the API * Rename * Update test for new save_testcase args, make test more robust * Simplify arguments related to generate_testcase_callback by removing default arg. Add test for this behavior * Minor test fixup * Add official API docs * Remove unnecessary new event type
This commit is contained in:
@@ -20,7 +20,7 @@ State
|
||||
-----
|
||||
|
||||
.. autoclass:: manticore.core.state.State
|
||||
:members: abandon, constrain, new_symbolic_buffer, new_symbolic_value, solve_n, solve_one, solve_buffer, symbolicate_buffer, invoke_model
|
||||
:members: abandon, constrain, new_symbolic_buffer, new_symbolic_value, solve_n, solve_one, solve_buffer, symbolicate_buffer, invoke_model, generate_testcase
|
||||
|
||||
Cpu
|
||||
---
|
||||
|
||||
@@ -284,7 +284,7 @@ class Executor(Eventful):
|
||||
|
||||
#broadcast test generation. This is the time for other modules
|
||||
#to output whatever helps to understand this testcase
|
||||
self.publish('will_generate_testcase', state, message)
|
||||
self.publish('will_generate_testcase', state, 'test', message)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -364,3 +364,12 @@ class State(Eventful):
|
||||
|
||||
def _init_context(self):
|
||||
self.context['branches'] = dict()
|
||||
|
||||
def generate_testcase(self, name, message='State generated testcase'):
|
||||
"""
|
||||
Generate a testcase for this state and place in the analysis workspace.
|
||||
|
||||
:param str name: Short string identifying this testcase used to prefix workspace entries.
|
||||
:param str message: Longer description
|
||||
"""
|
||||
self.publish('will_generate_inputs', name, message)
|
||||
|
||||
@@ -400,6 +400,7 @@ class ManticoreOutput(object):
|
||||
|
||||
:param desc: A descriptor ('type:uri') of where to write output.
|
||||
"""
|
||||
self._named_key_prefix = 'test'
|
||||
self._store = _create_store(desc)
|
||||
self._last_id = 0
|
||||
self._id_gen = manager.Value('i', self._last_id)
|
||||
@@ -415,9 +416,9 @@ class ManticoreOutput(object):
|
||||
self._id_gen.value += 1
|
||||
|
||||
def _named_key(self, suffix):
|
||||
return 'test_{:08x}.{}'.format(self._last_id, suffix)
|
||||
return '{}_{:08x}.{}'.format(self._named_key_prefix, self._last_id, suffix)
|
||||
|
||||
def save_testcase(self, state, message=''):
|
||||
def save_testcase(self, state, prefix, message=''):
|
||||
"""
|
||||
Save the environment from `state` to storage. Return a state id
|
||||
describing it, which should be an int or a string.
|
||||
@@ -427,6 +428,7 @@ class ManticoreOutput(object):
|
||||
:return: A state id representing the saved state
|
||||
"""
|
||||
|
||||
self._named_key_prefix = prefix
|
||||
self._increment_id()
|
||||
|
||||
self.save_summary(state, message)
|
||||
|
||||
@@ -607,13 +607,13 @@ class Manticore(object):
|
||||
state.context['instructions_count'] = count + 1
|
||||
|
||||
|
||||
def _generate_testcase_callback(self, state, message):
|
||||
def _generate_testcase_callback(self, state, name, message):
|
||||
'''
|
||||
Create a serialized description of a given state.
|
||||
:param state: The state to generate information about
|
||||
:param message: Accompanying message
|
||||
'''
|
||||
testcase_id = self._output.save_testcase(state, message)
|
||||
testcase_id = self._output.save_testcase(state, name, message)
|
||||
logger.info("Generated testcase No. {} - {}".format(testcase_id, message))
|
||||
|
||||
def _produce_profiling_data(self):
|
||||
|
||||
@@ -46,8 +46,9 @@ class Eventful(object):
|
||||
#A bucket is a dictionary obj -> set(method1, method2...)
|
||||
return self._signals.setdefault(name, dict())
|
||||
|
||||
def publish(self, name, *args, **kwargs):
|
||||
bucket = self._get_signal_bucket(name)
|
||||
# The underscore _name is to avoid naming collisions with callback params
|
||||
def publish(self, _name, *args, **kwargs):
|
||||
bucket = self._get_signal_bucket(_name)
|
||||
for robj, methods in bucket.items():
|
||||
for callback in methods:
|
||||
callback(robj(), *args, **kwargs)
|
||||
@@ -56,9 +57,9 @@ class Eventful(object):
|
||||
# the callback signature. This is set on forward_events_from/to
|
||||
for sink, include_source in self._forwards.items():
|
||||
if include_source:
|
||||
sink.publish(name, self, *args, **kwargs)
|
||||
sink.publish(_name, self, *args, **kwargs)
|
||||
else:
|
||||
sink.publish(name, *args, **kwargs)
|
||||
sink.publish(_name, *args, **kwargs)
|
||||
|
||||
def subscribe(self, name, method):
|
||||
if not inspect.ismethod(method):
|
||||
|
||||
@@ -86,8 +86,18 @@ class StateTest(unittest.TestCase):
|
||||
|
||||
def test_output(self):
|
||||
out = ManticoreOutput('mem:')
|
||||
out.save_testcase(self.state, 'saving state')
|
||||
keys = [x[14:] for x in out._store._data.keys()]
|
||||
name = 'mytest'
|
||||
message = 'custom message'
|
||||
out.save_testcase(self.state, name, message)
|
||||
workspace = out._store._data
|
||||
|
||||
# Make sure names are constructed correctly
|
||||
for name, data in workspace.items():
|
||||
self.assertTrue(name.startswith(name))
|
||||
if 'messages' in name:
|
||||
self.assertTrue(message in data)
|
||||
|
||||
keys = [x.split('.')[1] for x in workspace.keys()]
|
||||
|
||||
# Make sure we log everything we should be logging
|
||||
self.assertIn('smt', keys)
|
||||
|
||||
Reference in New Issue
Block a user