Skip to content

Commit 5a2fdaf

Browse files
committed
Improve project profile, adds more information and visual cleanup.
Do not display recent_data if there is none, this caused misleading tables and empty plots for non C/C++ projects. Collect and display corpus size. Visual cleanup adds more space for table and scroll box for plots. Add quick links to quickly access plots from per target table. Add links for day to day degradation. Signed-off-by: phi-go <[email protected]>
1 parent 64cc9c8 commit 5a2fdaf

File tree

4 files changed

+671
-451
lines changed

4 files changed

+671
-451
lines changed

tools/web-fuzzing-introspection/app/static/assets/db/oss_fuzz.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,23 @@ def get_introspector_report_url_debug_info(project_name, datestr):
180180
datestr) + "all_debug_info.json"
181181

182182

183+
def get_introspector_report_url_fuzzer_log_file(project_name, datestr, fuzzer):
184+
return get_introspector_report_url_base(
185+
project_name, datestr) + f"fuzzerLogFile-{fuzzer}.data.yaml"
186+
187+
188+
def get_introspector_report_url_fuzzer_program_data(project_name, datestr,
189+
program_data_filename):
190+
return get_introspector_report_url_base(project_name,
191+
datestr) + program_data_filename
192+
193+
194+
def get_introspector_report_url_fuzzer_coverage_urls(project_name, datestr,
195+
coverage_files):
196+
prefix = get_introspector_report_url_base(project_name, datestr)
197+
return [prefix + ff for ff in coverage_files]
198+
199+
183200
def extract_introspector_debug_info(project_name, date_str):
184201
debug_data_url = get_introspector_report_url_debug_info(
185202
project_name, date_str.replace("-", ""))
@@ -281,6 +298,55 @@ def get_fuzzer_code_coverage_summary(project_name, datestr, fuzzer):
281298
return None
282299

283300

301+
MAGNITUDES = {
302+
"k": 10**(3 * 1),
303+
"M": 10**(3 * 2),
304+
"G": 10**(3 * 3),
305+
"T": 10**(3 * 4),
306+
"P": 10**(3 * 5),
307+
"E": 10**(3 * 6),
308+
"Z": 10**(3 * 7),
309+
"Y": 10**(3 * 8),
310+
}
311+
312+
313+
def get_fuzzer_corpus_size(project_name, datestr, fuzzer, introspector_report):
314+
"""Go through coverage reports to find the LLVMFuzzerTestOneInput function. The first hit count equals the number inputs found."""
315+
316+
metadata_files = introspector_report[fuzzer]["metadata-files"]
317+
318+
fuzzer_program_coverage_urls = get_introspector_report_url_fuzzer_coverage_urls(
319+
project_name, datestr, metadata_files["coverage"])
320+
321+
for url in fuzzer_program_coverage_urls:
322+
found = False
323+
try:
324+
cov_res = requests.get(url, timeout=20).text
325+
for ll in cov_res.splitlines():
326+
if found:
327+
# Letters used is implemented here:
328+
# https://github.com/llvm/llvm-project/blob/7569de527298a52618239ef68b9374a5c35c8b97/llvm/tools/llvm-cov/SourceCoverageView.cpp#L117
329+
# Used from here:
330+
# https://github.com/llvm/llvm-project/blob/35ed9a32d58bc8cbace31dc7c3bba79d0e3a9256/llvm/tools/llvm-cov/SourceCoverageView.h#L269
331+
try:
332+
count_str = ll.split("|")[1].strip()
333+
magnitude_char = count_str[-1]
334+
if magnitude_char.isalpha():
335+
magnitude = MAGNITUDES[magnitude_char]
336+
count = float(count_str[:-1])
337+
else:
338+
magnitude = 1
339+
count = float(count_str)
340+
return int(magnitude * count)
341+
except:
342+
# Something went wrong, maybe another file has correct data.
343+
break
344+
if ll == "LLVMFuzzerTestOneInput:":
345+
found = True
346+
except:
347+
return None
348+
349+
284350
def extract_new_introspector_functions(project_name, date_str):
285351
introspector_functions_url = get_introspector_report_url_all_functions(
286352
project_name, date_str.replace("-", ""))
@@ -372,7 +438,7 @@ def extract_introspector_report(project_name, date_str):
372438
introspector_report_url = get_introspector_report_url_report(
373439
project_name, date_str.replace("-", ""))
374440

375-
# Read the introspector atifact
441+
# Read the introspector artifact
376442
try:
377443
raw_introspector_json_request = requests.get(introspector_summary_url,
378444
timeout=10)

tools/web-fuzzing-introspection/app/static/assets/db/web_db_creator_from_summary.py

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ def extract_code_coverage_data(code_coverage_summary):
325325
return line_total_summary
326326

327327

328-
def prepare_code_coverage_dict(
328+
def prepare_code_coverage_data(
329329
code_coverage_summary, project_name: str, date_str: str,
330330
project_language: str) -> Optional[Dict[str, Any]]:
331331
"""Gets coverage URL and line coverage total of a project"""
@@ -472,7 +472,7 @@ def extract_local_project_data(project_name, oss_fuzz_path,
472472
project_name
473473
}
474474

475-
code_coverage_data_dict = prepare_code_coverage_dict(
475+
code_coverage_data_dict = prepare_code_coverage_data(
476476
code_coverage_summary, project_name, '', project_language)
477477

478478
if cov_fuzz_stats is not None:
@@ -737,7 +737,7 @@ def extract_project_data(project_name, date_str, should_include_details,
737737
'project_name': project_name
738738
}
739739

740-
code_coverage_data_dict = prepare_code_coverage_dict(
740+
code_coverage_data_dict = prepare_code_coverage_data(
741741
code_coverage_summary, project_name, date_str, project_language)
742742

743743
per_fuzzer_cov = {}
@@ -748,10 +748,19 @@ def extract_project_data(project_name, date_str, should_include_details,
748748

749749
amount_of_fuzzers = len(all_fuzzers)
750750
for ff in all_fuzzers:
751+
try:
752+
fuzzer_corpus_size = oss_fuzz.get_fuzzer_corpus_size(
753+
project_name, date_str.replace("-", ""), ff,
754+
introspector_report)
755+
except:
756+
fuzzer_corpus_size = None
757+
751758
try:
752759
fuzzer_cov = oss_fuzz.get_fuzzer_code_coverage_summary(
753760
project_name, date_str.replace("-", ""), ff)
754761
fuzzer_cov_data = extract_code_coverage_data(fuzzer_cov)
762+
if fuzzer_cov_data is not None:
763+
fuzzer_cov_data['corpus_size'] = fuzzer_corpus_size
755764
per_fuzzer_cov[ff] = fuzzer_cov_data
756765
except:
757766
pass
@@ -919,8 +928,36 @@ def extend_db_timestamps(db_timestamp, output_directory):
919928
json.dump(existing_timestamps, f)
920929

921930

931+
def per_fuzzer_coverage_has_degraded(fuzzer_data: List[Dict[str, Any]],
932+
project_name: str,
933+
ff: str) -> List[Dict[str, str]]:
934+
"""Go through the fuzzer data and find coverage drops."""
935+
936+
def get_url(date):
937+
report_url = oss_fuzz.get_fuzzer_code_coverage_summary_url(
938+
project_name, date.replace('-', ''), ff)
939+
report_url = report_url[:-len('summary.json')] + 'index.html'
940+
return report_url
941+
942+
res = []
943+
for yesterday, today in zip(fuzzer_data[:-1], fuzzer_data[1:]):
944+
if yesterday['percentage'] - today[
945+
'percentage'] > FUZZER_COVERAGE_IS_DEGRADED:
946+
res.append({
947+
'before_date': yesterday['date'],
948+
'before_url': get_url(yesterday['date']),
949+
'before_perc': yesterday['percentage'],
950+
'current_date': today['date'],
951+
'current_url': get_url(today['date']),
952+
'current_perc': today['percentage'],
953+
})
954+
955+
return res
956+
957+
922958
def per_fuzzer_coverage_analysis(project_name: str,
923-
coverages: Dict[str, List[Tuple[int, str]]],
959+
per_fuzzer_data: Dict[str, List[Dict[str,
960+
Any]]],
924961
lost_fuzzers):
925962
"""Go through the recent coverage results and combine them into a short summary.
926963
Including an assessment if the fuzzer got worse over time.
@@ -932,34 +969,47 @@ def per_fuzzer_coverage_analysis(project_name: str,
932969
# at per fuzzer coverage, which is should already be normalized to what
933970
# can be reached.
934971
# TODO What would be a good percentage to mark as coverage degradation,
935-
# taking 5% for now but should be observed, maybe per it should be
972+
# taking 5% for now but should be observed, maybe it should be
936973
# configurable per project as well.
937974
results = {}
938-
for ff, data in coverages.items():
975+
for ff, data in per_fuzzer_data.items():
939976
if len(data) > 0:
940-
values = [dd[0] for dd in data]
941-
dates = [dd[1] for dd in data]
942-
latest_date_with_value = next(dd[1] for dd in reversed(data)
943-
if dd[0] is not None)
977+
percentages = [dd['percentage'] for dd in data]
978+
dates = [dd['date'] for dd in data]
979+
totals = [dd['total'] for dd in data]
980+
covered = [dd['covered'] for dd in data]
981+
corpus_size = [dd['corpus_size'] for dd in data]
982+
latest_date_with_value = next(dd['date'] for dd in reversed(data)
983+
if dd['percentage'] is not None)
944984
if latest_date_with_value is not None:
945985
report_url = oss_fuzz.get_fuzzer_code_coverage_summary_url(
946986
project_name, latest_date_with_value.replace('-', ''), ff)
947987
report_url = report_url[:-len('summary.json')] + 'index.html'
948988
else:
949989
report_url = None
950-
max_cov = max(values[:-1], default=0)
951-
avg_cov = round(statistics.fmean(values), 2)
952-
current = values[-1]
990+
max_cov = max(percentages[:-1], default=0)
991+
avg_cov = round(statistics.fmean(percentages), 2)
992+
current = percentages[-1]
993+
try:
994+
days_degraded = per_fuzzer_coverage_has_degraded(
995+
data, project_name, ff)
996+
except:
997+
days_degraded = []
953998
results[ff] = {
954999
'report_url': report_url,
9551000
'report_date': latest_date_with_value,
956-
'coverages_values': values,
1001+
'hashed_name': str(hash(ff)),
1002+
'coverages_perc': percentages,
1003+
'coverages_totals': totals,
1004+
'coverages_covered': covered,
1005+
'coverages_corpus': corpus_size,
9571006
'coverages_dates': dates,
9581007
'max': max_cov,
9591008
'avg': avg_cov,
9601009
'current': current,
961-
'has_degraded':
1010+
'max_has_degraded':
9621011
(max_cov - current) > FUZZER_COVERAGE_IS_DEGRADED,
1012+
'days_degraded': days_degraded,
9631013
'got_lost': ff in lost_fuzzers,
9641014
}
9651015
return results
@@ -999,7 +1049,18 @@ def calculate_recent_results(projects_with_new_results, timestamps,
9991049
except:
10001050
perc = 0
10011051

1002-
per_fuzzer_coverages[ff].append((perc, do))
1052+
per_fuzzer_coverages[ff].append({
1053+
'corpus_size':
1054+
cov_data['corpus_size'],
1055+
'covered':
1056+
cov_data['covered'],
1057+
'total':
1058+
cov_data['count'],
1059+
'percentage':
1060+
perc,
1061+
'date':
1062+
do
1063+
})
10031064
except:
10041065
continue
10051066

@@ -1411,6 +1472,7 @@ def setup_webapp_cache() -> None:
14111472
os.mkdir("extracted-db-archive")
14121473

14131474
db_archive.extractall("extracted-db-archive")
1475+
14141476
logger.info("Extracted it all")
14151477

14161478
# Copy over the files

tools/web-fuzzing-introspection/app/webapp/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ def has_introspector(self) -> bool:
4646
return self.introspector_data is not None
4747

4848
def has_recent_results(self) -> bool:
49-
return self.recent_results is not None
49+
return self.recent_results is not None and sum(
50+
len(ff) for ff in self.recent_results) > 0
5051

5152

5253
class DBTimestamp:

0 commit comments

Comments
 (0)