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:
Mark Mossberg
2017-08-08 18:12:55 -04:00
committed by GitHub
parent 9ae76a4430
commit eef3cd20e2
7 changed files with 34 additions and 12 deletions

View File

@@ -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
---

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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):

View File

@@ -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):

View File

@@ -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)