diff --git a/src/python/bot/fuzzers/honggfuzz/engine.py b/src/python/bot/fuzzers/honggfuzz/engine.py index a11c1b08b3..1b77122897 100644 --- a/src/python/bot/fuzzers/honggfuzz/engine.py +++ b/src/python/bot/fuzzers/honggfuzz/engine.py @@ -19,6 +19,7 @@ from bot.fuzzers import dictionary_manager from bot.fuzzers import engine +from metrics import logs from system import environment from system import new_process @@ -41,6 +42,7 @@ _CRASH_REGEX = re.compile('Crash: saved as \'(.*)\'') _HF_SANITIZER_LOG_PREFIX = 'HF.sanitizer.log' +_STATS_PREFIX = 'Summary ' class HonggfuzzError(Exception): @@ -66,6 +68,36 @@ def _find_sanitizer_stacktrace(reproducers_dir): return None +def _get_reproducer_path(line): + """Get the reproducer path, if any.""" + crash_match = _CRASH_REGEX.match(line) + if not crash_match: + return None + + return crash_match.group(1) + + +def _get_stats(line): + """Get stats, if any.""" + if not line.startswith(_STATS_PREFIX): + return None + + parts = line[len(_STATS_PREFIX):].split() + stats = {} + + for part in parts: + if ':' not in part: + logs.log_error('Invalid stat part.', value=part) + + key, value = part.split(':', 2) + try: + stats[key] = int(value) + except (ValueError, TypeError): + logs.log_error('Invalid stat value.', key=key, value=value) + + return stats + + class HonggfuzzEngine(engine.Engine): """honggfuzz engine implementation.""" @@ -125,20 +157,22 @@ def fuzz(self, target_path, options, reproducers_dir, max_time): sanitizer_stacktrace = _find_sanitizer_stacktrace(reproducers_dir) crashes = [] + stats = None for line in log_lines: - crash_match = _CRASH_REGEX.match(line) - if not crash_match: + reproducer_path = _get_reproducer_path(line) + if reproducer_path: + crashes.append( + engine.Crash(reproducer_path, sanitizer_stacktrace, [], + int(fuzz_result.time_executed))) continue - reproducer_path = crash_match.group(1) - crashes.append( - engine.Crash(reproducer_path, sanitizer_stacktrace, [], - int(fuzz_result.time_executed))) - break + stats = _get_stats(line) + + if stats is None: + stats = {} - # TODO(ochang): Parse stats. return engine.FuzzResult(fuzz_result.output, fuzz_result.command, crashes, - {}, fuzz_result.time_executed) + stats, fuzz_result.time_executed) def reproduce(self, target_path, input_path, arguments, max_time): """Reproduce a crash given an input. diff --git a/src/python/tests/core/bot/fuzzers/honggfuzz/honggfuzz_engine_test.py b/src/python/tests/core/bot/fuzzers/honggfuzz/honggfuzz_engine_test.py index f06d433a7a..8daa80a84e 100644 --- a/src/python/tests/core/bot/fuzzers/honggfuzz/honggfuzz_engine_test.py +++ b/src/python/tests/core/bot/fuzzers/honggfuzz/honggfuzz_engine_test.py @@ -63,6 +63,19 @@ def setUp(self): os.environ['BUILD_DIR'] = DATA_DIR + def assert_has_stats(self, results): + """Assert that stats exist.""" + self.assertIn('iterations', results.stats) + self.assertIn('time', results.stats) + self.assertIn('speed', results.stats) + self.assertIn('crashes_count', results.stats) + self.assertIn('timeout_count', results.stats) + self.assertIn('new_units_added', results.stats) + self.assertIn('slowest_unit_ms', results.stats) + self.assertIn('guard_nb', results.stats) + self.assertIn('branch_coverage_percent', results.stats) + self.assertIn('peak_rss_mb', results.stats) + def test_reproduce(self): """Tests reproducing a crash.""" testcase_path, _ = setup_testcase_and_corpus('crash', 'empty_corpus') @@ -107,6 +120,7 @@ def test_fuzz_no_crash(self): ], results.command) self.assertGreater(len(os.listdir(corpus_path)), 0) + self.assert_has_stats(results) def test_fuzz_crash(self): """Test fuzzing that results in a crash.""" @@ -148,3 +162,5 @@ def test_fuzz_crash(self): with open(crash.input_path) as f: self.assertEqual('A', f.read()[0]) + + self.assert_has_stats(results)