Skip to content

Commit fc2a3da

Browse files
authored
Improve project profile, adds more information and visual cleanup. (#2162)
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 b5c7218 commit fc2a3da

File tree

4 files changed

+688
-451
lines changed

4 files changed

+688
-451
lines changed

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

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,23 @@ def get_introspector_report_url_debug_info(project_name, datestr):
221221
datestr) + "all_debug_info.json"
222222

223223

224+
def get_introspector_report_url_fuzzer_log_file(project_name, datestr, fuzzer):
225+
return get_introspector_report_url_base(
226+
project_name, datestr) + f"fuzzerLogFile-{fuzzer}.data.yaml"
227+
228+
229+
def get_introspector_report_url_fuzzer_program_data(project_name, datestr,
230+
program_data_filename):
231+
return get_introspector_report_url_base(project_name,
232+
datestr) + program_data_filename
233+
234+
235+
def get_introspector_report_url_fuzzer_coverage_urls(project_name, datestr,
236+
coverage_files):
237+
prefix = get_introspector_report_url_base(project_name, datestr)
238+
return [prefix + ff for ff in coverage_files]
239+
240+
224241
def extract_introspector_debug_info(project_name, date_str):
225242
debug_data_url = get_introspector_report_url_debug_info(
226243
project_name, date_str.replace("-", ""))
@@ -368,6 +385,59 @@ def get_fuzzer_code_coverage_summary(project_name, datestr, fuzzer):
368385
return None
369386

370387

388+
MAGNITUDES = {
389+
"k": 10**(3 * 1),
390+
"M": 10**(3 * 2),
391+
"G": 10**(3 * 3),
392+
"T": 10**(3 * 4),
393+
"P": 10**(3 * 5),
394+
"E": 10**(3 * 6),
395+
"Z": 10**(3 * 7),
396+
"Y": 10**(3 * 8),
397+
}
398+
399+
400+
def get_fuzzer_corpus_size(project_name, datestr, fuzzer, introspector_report):
401+
"""Go through coverage reports to find the LLVMFuzzerTestOneInput function. The first hit count equals the number inputs found."""
402+
403+
if introspector_report["MergedProjectProfile"]["overview"][
404+
"language"] != "c-cpp":
405+
return None
406+
407+
metadata_files = introspector_report[fuzzer]["metadata-files"]
408+
409+
fuzzer_program_coverage_urls = get_introspector_report_url_fuzzer_coverage_urls(
410+
project_name, datestr, metadata_files["coverage"])
411+
412+
for url in fuzzer_program_coverage_urls:
413+
found = False
414+
try:
415+
cov_res = requests.get(url, timeout=20).text
416+
for ll in cov_res.splitlines():
417+
if found:
418+
# Letters used is implemented here:
419+
# https://github.com/llvm/llvm-project/blob/7569de527298a52618239ef68b9374a5c35c8b97/llvm/tools/llvm-cov/SourceCoverageView.cpp#L117
420+
# Used from here:
421+
# https://github.com/llvm/llvm-project/blob/35ed9a32d58bc8cbace31dc7c3bba79d0e3a9256/llvm/tools/llvm-cov/SourceCoverageView.h#L269
422+
try:
423+
count_str = ll.split("|")[1].strip()
424+
magnitude_char = count_str[-1]
425+
if magnitude_char.isalpha():
426+
magnitude = MAGNITUDES[magnitude_char]
427+
count = float(count_str[:-1])
428+
else:
429+
magnitude = 1
430+
count = float(count_str)
431+
return int(magnitude * count)
432+
except:
433+
# Something went wrong, maybe another file has correct data.
434+
break
435+
if ll == "LLVMFuzzerTestOneInput:":
436+
found = True
437+
except:
438+
return None
439+
440+
371441
def extract_new_introspector_functions(project_name, date_str):
372442
introspector_functions_url = get_introspector_report_url_all_functions(
373443
project_name, date_str.replace("-", ""))
@@ -511,7 +581,7 @@ def extract_introspector_report(project_name, date_str):
511581
introspector_report_url = get_introspector_report_url_report(
512582
project_name, date_str.replace("-", ""))
513583

514-
# Read the introspector atifact
584+
# Read the introspector artifact
515585
try:
516586
raw_introspector_json_request = requests.get(introspector_summary_url,
517587
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
@@ -334,7 +334,7 @@ def extract_code_coverage_data(code_coverage_summary):
334334
return line_total_summary
335335

336336

337-
def prepare_code_coverage_dict(
337+
def prepare_code_coverage_data(
338338
code_coverage_summary, project_name: str, date_str: str,
339339
project_language: str) -> Optional[Dict[str, Any]]:
340340
"""Gets coverage URL and line coverage total of a project"""
@@ -498,7 +498,7 @@ def extract_local_project_data(project_name, oss_fuzz_path,
498498
macro_block
499499
}
500500

501-
code_coverage_data_dict = prepare_code_coverage_dict(
501+
code_coverage_data_dict = prepare_code_coverage_data(
502502
code_coverage_summary, project_name, '', project_language)
503503

504504
if cov_fuzz_stats is not None:
@@ -776,7 +776,7 @@ def extract_project_data(project_name, date_str, should_include_details,
776776
'macro_block': macro_block
777777
}
778778

779-
code_coverage_data_dict = prepare_code_coverage_dict(
779+
code_coverage_data_dict = prepare_code_coverage_data(
780780
code_coverage_summary, project_name, date_str, project_language)
781781

782782
per_fuzzer_cov = {}
@@ -787,10 +787,19 @@ def extract_project_data(project_name, date_str, should_include_details,
787787

788788
amount_of_fuzzers = len(all_fuzzers)
789789
for ff in all_fuzzers:
790+
try:
791+
fuzzer_corpus_size = oss_fuzz.get_fuzzer_corpus_size(
792+
project_name, date_str.replace("-", ""), ff,
793+
introspector_report)
794+
except:
795+
fuzzer_corpus_size = None
796+
790797
try:
791798
fuzzer_cov = oss_fuzz.get_fuzzer_code_coverage_summary(
792799
project_name, date_str.replace("-", ""), ff)
793800
fuzzer_cov_data = extract_code_coverage_data(fuzzer_cov)
801+
if fuzzer_cov_data is not None:
802+
fuzzer_cov_data['corpus_size'] = fuzzer_corpus_size
794803
per_fuzzer_cov[ff] = fuzzer_cov_data
795804
except:
796805
pass
@@ -958,8 +967,36 @@ def extend_db_timestamps(db_timestamp, output_directory):
958967
json.dump(existing_timestamps, f)
959968

960969

970+
def per_fuzzer_coverage_has_degraded(fuzzer_data: List[Dict[str, Any]],
971+
project_name: str,
972+
ff: str) -> List[Dict[str, str]]:
973+
"""Go through the fuzzer data and find coverage drops."""
974+
975+
def get_url(date):
976+
report_url = oss_fuzz.get_fuzzer_code_coverage_summary_url(
977+
project_name, date.replace('-', ''), ff)
978+
report_url = report_url[:-len('summary.json')] + 'index.html'
979+
return report_url
980+
981+
res = []
982+
for yesterday, today in zip(fuzzer_data[:-1], fuzzer_data[1:]):
983+
if yesterday['percentage'] - today[
984+
'percentage'] > FUZZER_COVERAGE_IS_DEGRADED:
985+
res.append({
986+
'before_date': yesterday['date'],
987+
'before_url': get_url(yesterday['date']),
988+
'before_perc': yesterday['percentage'],
989+
'current_date': today['date'],
990+
'current_url': get_url(today['date']),
991+
'current_perc': today['percentage'],
992+
})
993+
994+
return res
995+
996+
961997
def per_fuzzer_coverage_analysis(project_name: str,
962-
coverages: Dict[str, List[Tuple[int, str]]],
998+
per_fuzzer_data: Dict[str, List[Dict[str,
999+
Any]]],
9631000
lost_fuzzers):
9641001
"""Go through the recent coverage results and combine them into a short summary.
9651002
Including an assessment if the fuzzer got worse over time.
@@ -971,34 +1008,47 @@ def per_fuzzer_coverage_analysis(project_name: str,
9711008
# at per fuzzer coverage, which is should already be normalized to what
9721009
# can be reached.
9731010
# TODO What would be a good percentage to mark as coverage degradation,
974-
# taking 5% for now but should be observed, maybe per it should be
1011+
# taking 5% for now but should be observed, maybe it should be
9751012
# configurable per project as well.
9761013
results = {}
977-
for ff, data in coverages.items():
1014+
for ff, data in per_fuzzer_data.items():
9781015
if len(data) > 0:
979-
values = [dd[0] for dd in data]
980-
dates = [dd[1] for dd in data]
981-
latest_date_with_value = next(dd[1] for dd in reversed(data)
982-
if dd[0] is not None)
1016+
percentages = [dd['percentage'] for dd in data]
1017+
dates = [dd['date'] for dd in data]
1018+
totals = [dd['total'] for dd in data]
1019+
covered = [dd['covered'] for dd in data]
1020+
corpus_size = [dd['corpus_size'] for dd in data]
1021+
latest_date_with_value = next(dd['date'] for dd in reversed(data)
1022+
if dd['percentage'] is not None)
9831023
if latest_date_with_value is not None:
9841024
report_url = oss_fuzz.get_fuzzer_code_coverage_summary_url(
9851025
project_name, latest_date_with_value.replace('-', ''), ff)
9861026
report_url = report_url[:-len('summary.json')] + 'index.html'
9871027
else:
9881028
report_url = None
989-
max_cov = max(values[:-1], default=0)
990-
avg_cov = round(statistics.fmean(values), 2)
991-
current = values[-1]
1029+
max_cov = max(percentages[:-1], default=0)
1030+
avg_cov = round(statistics.fmean(percentages), 2)
1031+
current = percentages[-1]
1032+
try:
1033+
days_degraded = per_fuzzer_coverage_has_degraded(
1034+
data, project_name, ff)
1035+
except:
1036+
days_degraded = []
9921037
results[ff] = {
9931038
'report_url': report_url,
9941039
'report_date': latest_date_with_value,
995-
'coverages_values': values,
1040+
'hashed_name': str(hash(ff)),
1041+
'coverages_perc': percentages,
1042+
'coverages_totals': totals,
1043+
'coverages_covered': covered,
1044+
'coverages_corpus': corpus_size,
9961045
'coverages_dates': dates,
9971046
'max': max_cov,
9981047
'avg': avg_cov,
9991048
'current': current,
1000-
'has_degraded':
1049+
'max_has_degraded':
10011050
(max_cov - current) > FUZZER_COVERAGE_IS_DEGRADED,
1051+
'days_degraded': days_degraded,
10021052
'got_lost': ff in lost_fuzzers,
10031053
}
10041054
return results
@@ -1038,7 +1088,18 @@ def calculate_recent_results(projects_with_new_results, timestamps,
10381088
except:
10391089
perc = 0
10401090

1041-
per_fuzzer_coverages[ff].append((perc, do))
1091+
per_fuzzer_coverages[ff].append({
1092+
'corpus_size':
1093+
cov_data['corpus_size'],
1094+
'covered':
1095+
cov_data['covered'],
1096+
'total':
1097+
cov_data['count'],
1098+
'percentage':
1099+
perc,
1100+
'date':
1101+
do
1102+
})
10421103
except:
10431104
continue
10441105

@@ -1450,6 +1511,7 @@ def setup_webapp_cache() -> None:
14501511
os.mkdir("extracted-db-archive")
14511512

14521513
db_archive.extractall("extracted-db-archive")
1514+
14531515
logger.info("Extracted it all")
14541516

14551517
# 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)