Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
f9e7003
fix typo
phi-go Sep 22, 2023
aaa049c
add mutation_analysis image with gllvm
phi-go Sep 26, 2023
e90cd53
[WIP] mua framework integration
phi-go Oct 2, 2023
a0e2b1f
[WIP] update-alternatives in mua image
phi-go Oct 4, 2023
aeee7a6
[WIP] integrate mutation analysis into fuzzbench, build mua image
vandaltool Oct 12, 2023
e994f65
[WIP] mua integration fuzzbench
vandaltool Oct 13, 2023
ffbf1e4
[WIP] mua integration recover changes
vandaltool Oct 28, 2023
40cad5e
fuzzbench integration: build mutants
vandaltool Nov 6, 2023
396140e
add mua command line option
vandaltool Nov 6, 2023
b070a60
move mua code from local only to local+gcb
vandaltool Nov 14, 2023
f1e8c26
add process_mua
vandaltool Nov 15, 2023
8a76d1f
faster starts for dev
phi-go Nov 16, 2023
e2768cb
pass presubmit checks
phi-go Dec 12, 2023
6f731ff
Merge branch 'google:master' into sbft
phi-go Dec 12, 2023
02039ab
impl build_mua for gcb_build (untested)
phi-go Dec 12, 2023
340244b
Merge branch 'google:master' into sbft
phi-go Dec 22, 2023
67d685d
Merge branch 'google:master' into sbft
phi-go Dec 23, 2023
07a45f5
changes fixing performance and cloud issues
phi-go Dec 22, 2023
7ab3ef8
Fix OpenH264 based on OSS-Fuzz
DonggeLiu Dec 23, 2023
12a54f9
just test snapshot_coverage without mua
phi-go Dec 25, 2023
d1f5cef
write mua results to experiment store
phi-go Dec 25, 2023
67ce1d7
store report errors in experiment-data
phi-go Dec 26, 2023
fc812a6
fix presubmit checks
phi-go Dec 26, 2023
800ebd9
actually fail when docker build for mua fails
phi-go Dec 27, 2023
7cdd014
use more robust mua_build version
phi-go Dec 27, 2023
9ba0b60
update mua build scripts
phi-go Dec 28, 2023
f5b382b
remove spammy log
phi-go Dec 28, 2023
33089c3
improve logging output
phi-go Dec 28, 2023
5bd783f
escape db password in dispatcher startup script
phi-go Dec 28, 2023
e5482c0
log build_mua config
phi-go Dec 28, 2023
019da82
log config for base images
phi-go Dec 28, 2023
db334f8
no need to log gcb build configs
phi-go Dec 28, 2023
a2358c2
make host_mua_out_dir configurable
phi-go Dec 28, 2023
6f2d549
build mua measurer images only if needed
phi-go Dec 28, 2023
ec1095e
cloud shared dir and error message mua run
phi-go Dec 28, 2023
ed29c4a
pass presubmit
phi-go Dec 28, 2023
9830393
log more for permission errors
phi-go Dec 29, 2023
56bb28c
debugging mua_run_mutants
phi-go Dec 29, 2023
bbbd3a3
fix for subprocess run
phi-go Dec 29, 2023
4ca3f04
logging for permission error
phi-go Dec 29, 2023
1edbb02
use different mua out dir on cloud
phi-go Dec 29, 2023
a202018
use another mua out dir
phi-go Dec 29, 2023
636991b
vacuum sqlite db instead of simple cp
phi-go Dec 29, 2023
d3649ca
improve logging a bit
phi-go Dec 29, 2023
be4d7d9
pass presubmit
phi-go Dec 29, 2023
6e3f73c
fix benchmark re2_fuzzer cflags
vandaltool Dec 30, 2023
54a2d2b
improve mua run / build scripts
phi-go Dec 30, 2023
5f33adf
improve performance of mua measurer
phi-go Jan 2, 2024
0702bbb
improve mua run perf
phi-go Jan 3, 2024
7f7b67e
dispatcher local and mua wait for trials
phi-go Jan 3, 2024
fe9fd38
pass presubmit
phi-go Jan 3, 2024
72458de
extra logging for local run
phi-go Jan 3, 2024
392b981
fix libFuzzingEngineMutation support for openssl
vandaltool Jan 4, 2024
c879b43
fix libFuzzingEngineMutation.a not compiled lib bug
vandaltool Jan 4, 2024
724cbe0
fix pip not found problem + mua version bump
vandaltool Jan 4, 2024
a69b5c7
set main.cc as FUZZER_LIB again
vandaltool Jan 4, 2024
6779753
mua run perf improvement and compress result dbs
phi-go Jan 4, 2024
59c41ae
pass presubmit and update mua_fuzzer_bench version
phi-go Jan 4, 2024
72ed9c6
ulimit in mua run and better error logs
phi-go Jan 5, 2024
11da652
improve mua startup and resource allocation
phi-go Jan 9, 2024
cc85e8b
perf improvements
phi-go Jan 10, 2024
1c5a5a6
less spam in mua runs
phi-go Jan 10, 2024
d078a9d
overwrite results db on store
phi-go Jan 10, 2024
a126e95
only eval median trial
phi-go Jan 11, 2024
93ef805
less spam when waiting on trials
phi-go Jan 12, 2024
5239d94
Integrate mutation testing into fuzzbench reporting
vandaltool Jan 12, 2024
29539db
pass presubmit
phi-go Jan 12, 2024
fcf342c
fix running without mutation-analysis flag
phi-go Jan 15, 2024
72926c0
set merge_with_nonprivate to false
phi-go Jan 15, 2024
47af5a9
store build logs for local runs
phi-go Jan 17, 2024
b8672c8
allow build scripts compiling same binary twice
phi-go Jan 17, 2024
0452521
allow unlimited log size for gcb_build
phi-go Jan 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@
.venv
**__pycache__*
docs
report*
report*
fuzzers/mutation_analysis/mua_fuzzer_bench/.git
fuzzers/mutation_analysis/fuzzbench_mapped_dir/
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,7 @@ docker/generated.mk

# Vim backup files.
.*.swp

# mua related files and directories.
fuzzers/mutation_analysis/fuzzbench_mapped_dir/
mua_out/
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[submodule "fuzzers/mutation_analysis/mua_fuzzer_bench"]
path = fuzzers/mutation_analysis/mua_fuzzer_bench
url = https://github.com/phi-go/mua_fuzzer_bench
branch = sbft
28 changes: 27 additions & 1 deletion analysis/benchmark_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
from analysis import stat_tests
from common import benchmark_utils
from common import filestore_utils
from common import logs

logger = logs.Logger()


# pylint: disable=too-many-public-methods, too-many-arguments
Expand All @@ -36,13 +39,14 @@ class BenchmarkResults:
"""

def __init__(self, benchmark_name, experiment_df, coverage_dict,
output_directory, plotter):
output_directory, plotter, mua_results):
self.name = benchmark_name

self._experiment_df = experiment_df
self._coverage_dict = coverage_dict
self._output_directory = output_directory
self._plotter = plotter
self.mua_results = mua_results

def _prefix_with_benchmark(self, filename):
return self.name + '_' + filename
Expand All @@ -56,6 +60,28 @@ def get_coverage_report_path(self, fuzzer_name, benchmark_name):
fuzzer_name, benchmark_name, self._benchmark_df)
return filestore_utils.get_user_facing_path(filestore_path)

def get_mua_report_data(self, _fuzzer_name, _benchmark_name):
"""Returns results as string"""
return 'TODO: Not Implemented Yet' #TODO: implement this or delete

@property
def mutation_analysis_plot(self):
"""plot for mutation analysis."""
plot_filename = self._prefix_with_benchmark('mutation_analysis.svg')
if self.mua_results is None:
logger.info(
'mutation_analysis_plot not rendered, due to missing data')
return None

(num_trials, fuzzer_pds) = self.mua_results

num_fuzzers = len(fuzzer_pds)

self._plotter.write_mutation_analysis_plot(
fuzzer_pds, num_fuzzers, num_trials,
self._get_full_path(plot_filename))
return plot_filename

@property
@functools.lru_cache()
def type(self):
Expand Down
5 changes: 4 additions & 1 deletion analysis/experiment_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,16 @@ def __init__( # pylint: disable=too-many-arguments
coverage_dict,
output_directory,
plotter,
mua_results=None,
experiment_name=None):
if experiment_name:
self.name = experiment_name
else:
# Take name from first row.
self.name = experiment_df.experiment.iloc[0]

self.mua_results = mua_results

# FuzzBench repo commit hash.
self.git_hash = None
if 'git_hash' in experiment_df.columns:
Expand Down Expand Up @@ -135,7 +138,7 @@ def benchmarks(self):
benchmark_results.BenchmarkResults(name, self._experiment_df,
self._coverage_dict,
self._output_directory,
self._plotter)
self._plotter, self.mua_results)
for name in sorted(benchmark_names)
]

Expand Down
193 changes: 190 additions & 3 deletions analysis/generate_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@
"""Report generator tool."""

import argparse
import lzma
import os
import sys
import sqlite3
import tempfile

from collections import defaultdict

import pandas as pd

Expand All @@ -27,6 +32,7 @@
from analysis import rendering
from common import filesystem
from common import logs
from common import experiment_utils

logger = logs.Logger()

Expand All @@ -44,7 +50,7 @@ def get_arg_parser():
parser.add_argument(
'-t',
'--report-type',
choices=['default', 'experimental'],
choices=['default', 'experimental', 'with_mua'],
default='default',
help='Type of the report (which template to use). Default: default.')
parser.add_argument(
Expand All @@ -58,6 +64,11 @@ def get_arg_parser():
action='store_true',
default=False,
help='If set, plots are created faster, but contain less details.')
parser.add_argument('-mua',
'--mutation-analysis',
action='store_true',
default=False,
help='If set, mutation analysis report is created.')
parser.add_argument(
'--log-scale',
action='store_true',
Expand All @@ -79,6 +90,11 @@ def get_arg_parser():
'--fuzzers',
nargs='*',
help='Names of the fuzzers to include in the report.')
parser.add_argument(
'-xb',
'--experiment-benchmarks',
nargs='*',
help='Names of the benchmarks to include in the report.')
parser.add_argument(
'-cov',
'--coverage-report',
Expand Down Expand Up @@ -142,6 +158,7 @@ def get_experiment_data(experiment_names,
logger.info('Reading experiment data from db.')
experiment_df = queries.get_experiment_data(experiment_names,
main_experiment_benchmarks)
# experiment_df.to_csv('/tmp/experiment-data/experiment_data.csv')
logger.info('Done reading experiment data from db.')
description = queries.get_experiment_description(main_experiment_name)
return experiment_df, description
Expand Down Expand Up @@ -186,6 +203,164 @@ def modify_experiment_data_if_requested( # pylint: disable=too-many-arguments
return experiment_df


def normalized_timestamps(timestamps):
"""Normalize timestamps."""
seed_timestamp_file = next(
(tt for tt in timestamps if tt[2] == '<seed_entry>'), None)
try:
min_timestamp_file = min(
(tt for tt in timestamps if tt[2] != '<seed_entry>'),
key=lambda x: x[3])
except ValueError:
min_timestamp_file = seed_timestamp_file
try:
max_timestamp_file = max(
(tt for tt in timestamps if tt[2] != '<seed_entry>'),
key=lambda x: x[3])
except ValueError:
max_timestamp_file = seed_timestamp_file
min_timestamp = min_timestamp_file[3]
max_timestamp = max_timestamp_file[3]
timestamps_normalized = {}
for _hashname, input_file_id, input_file, timestamp in timestamps:
if input_file == '<seed_entry>':
timestamps_normalized[input_file_id] = 0
else:
timestamps_normalized[input_file_id] = timestamp - min_timestamp

timespan = max_timestamp - min_timestamp
return timestamps_normalized, timespan


def get_first_covered_killed(results, timestamps_map):
"""Get first covered and killed mutant."""
ordered_inputs = sorted(results, key=lambda x: timestamps_map[x[0]])
mut_result_times = defaultdict(lambda: {'seen': None, 'killed': None})
for ordered_input in ordered_inputs:
input_file_id, mut_id, skipped, killed = ordered_input[:4]
if skipped:
continue
if mut_id not in mut_result_times:
mut_result_times[mut_id]['seen'] = timestamps_map[input_file_id]
if killed:
assert mut_id in mut_result_times
if mut_result_times[mut_id]['killed'] is None:
mut_result_times[mut_id]['killed'] = timestamps_map[
input_file_id]
return mut_result_times


def get_timeline(time_covered_killed, timespan, meta): # pylint: disable=too-many-locals
"""Create timeline regarding covering and killing of mutants."""
if timespan == 0:
max_time_base = 1
else:
max_time_base = 16
fuzz_target, benchmark, fuzzer_name, trial_num, cycle = meta
normalized_time_elem = timespan / (max_time_base**2)
time = 'time'
count_seen = 'seen'
count_killed = 'killed'
res = []
for time_base in range(1, max_time_base + 1):
time = normalized_time_elem * (time_base**2)
count_seen = 0
count_killed = 0
for _mut_id, times in time_covered_killed.items():
if times['seen'] is not None and times['seen'] <= time:
count_seen += 1
if times['killed'] is not None and times['killed'] <= time:
count_killed += 1
res.append((fuzz_target, benchmark, fuzzer_name, trial_num, cycle, time,
count_seen, count_killed))
return res


def load_result_db(res_db_path):
"""Load result.sqlite database."""
with tempfile.NamedTemporaryFile() as tmp_file:
with lzma.open(res_db_path) as res_db:
tmp_file.write(res_db.read())
tmp_file.flush()
tmp_file.seek(0)
with sqlite3.connect(tmp_file.name) as conn:
run_info = conn.execute(
'SELECT benchmark, fuzz_target, fuzzer, trial_num FROM run_info'
).fetchall()
results = conn.execute('''SELECT
input_file_id,
mut_id,
skipped,
killed,
orig_retcode,
mutant_retcode,
orig_runtime,
mutant_runtime,
orig_timed_out,
mutant_timed_out
FROM results''').fetchall()
timestamps = conn.execute(
'''SELECT hashname, input_file_id, input_file, timestamp
FROM timestamps''').fetchall()
return run_info, results, timestamps


def get_mua_results(experiment_df): # pylint: disable=too-many-locals
"""Get mutation analysis results for each fuzzer in each trial to use in
the report."""

#get relationship between trial_id and benchmark from df
trial_dict = experiment_df.set_index('trial_id')['benchmark'].to_dict()

experiment_data_dir = experiment_utils.get_experiment_filestore_path()
results_data_dir = f'{experiment_data_dir}/mua-results/results'

if not os.path.isdir(results_data_dir):
logger.warning('''mua-results/results dir does not exist,
stopping mua report creation''')
return None

fuzzer_pds = defaultdict(list)

for trial in trial_dict.keys():
mua_result_db_file = f'{results_data_dir}/{trial}/' \
'results.sqlite.lzma'
if not os.path.isfile(mua_result_db_file):
logger.debug(
'mua_result_db_file does not exist, this is expected ' +
'if only median trial is evaluated: '
f'{mua_result_db_file}')
continue
logger.info(f'found mua_result_db_file: {mua_result_db_file}')
run_info, results, timestamps = load_result_db(mua_result_db_file)
assert len(run_info) == 1
benchmark, fuzz_target, fuzzer, trial_num = run_info[0]
timestamps_map, timespan = normalized_timestamps(timestamps)

results = [
rr for rr in results if timestamps_map.get(rr[0]) is not None
]
time_covered_killed = get_first_covered_killed(results, timestamps_map)
meta = fuzz_target, benchmark, fuzzer, trial_num, trial
timeline = get_timeline(time_covered_killed, timespan, meta)
pd_timeline = pd.DataFrame(timeline,
columns=[
'fuzz_target', 'benchmark', 'fuzzer',
'trial_num', 'cycle', 'time', 'seen',
'killed'
])
fuzzer_pds[fuzzer].append(pd_timeline)

num_trials = None
for fuzzer, fuzzer_pd in fuzzer_pds.items():
if num_trials is None:
num_trials = len(fuzzer_pd)
else:
assert num_trials == len(fuzzer_pd)

return (num_trials, fuzzer_pds)


# pylint: disable=too-many-arguments,too-many-locals
def generate_report(experiment_names,
report_directory,
Expand All @@ -202,7 +377,8 @@ def generate_report(experiment_names,
merge_with_clobber=False,
merge_with_clobber_nonprivate=False,
coverage_report=False,
experiment_benchmarks=None):
experiment_benchmarks=None,
mutation_analysis=False):
"""Generate report helper."""
if merge_with_clobber_nonprivate:
experiment_names = (
Expand Down Expand Up @@ -231,6 +407,9 @@ def generate_report(experiment_names,
experiment_df, experiment_names, benchmarks, fuzzers,
label_by_experiment, end_time, merge_with_clobber)

# experiment_df.to_csv('/tmp/experiment-data/out.csv')

#TODO: make this work with a single fuzzer selected
# Add |bugs_covered| column prior to export.
experiment_df = data_utils.add_bugs_covered_column(experiment_df)

Expand All @@ -247,13 +426,19 @@ def generate_report(experiment_names,
experiment_df)
logger.info('Finished generating coverage report info.')

if mutation_analysis:
mua_results = get_mua_results(experiment_df)
else:
mua_results = None

fuzzer_names = experiment_df.fuzzer.unique()
plotter = plotting.Plotter(fuzzer_names, quick, log_scale)
experiment_ctx = experiment_results.ExperimentResults(
experiment_df,
coverage_dict,
report_directory,
plotter,
mua_results=mua_results,
experiment_name=report_name)

template = report_type + '.html'
Expand Down Expand Up @@ -286,7 +471,9 @@ def main():
from_cached_data=args.from_cached_data,
end_time=args.end_time,
merge_with_clobber=args.merge_with_clobber,
coverage_report=args.coverage_report)
coverage_report=args.coverage_report,
experiment_benchmarks=args.experiment_benchmarks,
mutation_analysis=args.mutation_analysis)


if __name__ == '__main__':
Expand Down
Loading