Skip to content

Commit 78c3c02

Browse files
authored
[Go] Add logic in FI (#1890)
* [Go] Add logic in FI Signed-off-by: Arthur Chan <[email protected]> * Fix formatting Signed-off-by: Arthur Chan <[email protected]> --------- Signed-off-by: Arthur Chan <[email protected]>
1 parent d7c3beb commit 78c3c02

File tree

4 files changed

+123
-4
lines changed

4 files changed

+123
-4
lines changed

src/fuzz_introspector/analysis.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,14 @@ def get_node_coverage_hitcount(demangled_name: str, callstack: Dict[int, str],
338338
hit_count_cov)
339339
if n_line_number == node.src_linenumber and hit_count_cov > 0:
340340
node_hitcount = hit_count_cov
341+
elif profile.target_lang == "go":
342+
coverage_data = profile.coverage.get_hit_details(
343+
callstack_get_parent(node, callstack))
344+
for (n_line_number, hit_count_cov) in coverage_data:
345+
logger.debug(" - iterating %d : %d", n_line_number,
346+
hit_count_cov)
347+
if n_line_number == node.src_linenumber and hit_count_cov > 0:
348+
node_hitcount = hit_count_cov
341349
node.cov_parent = callstack_get_parent(node, callstack)
342350
else:
343351
logger.error(
@@ -1018,6 +1026,8 @@ def extract_all_sources(language):
10181026
test_extensions = ['.py']
10191027
elif language == 'rust':
10201028
test_extensions = ['.rs']
1029+
elif language == 'go':
1030+
test_extensions = ['.go', '.cgo']
10211031
else:
10221032
test_extensions = ['.cc', '.cpp', '.cxx', '.c++', '.c', '.h', '.hpp']
10231033

src/fuzz_introspector/code_coverage.py

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
from fuzz_introspector import utils
3232
from fuzz_introspector import exceptions
33+
from fuzz_introspector.datatypes import function_profile
3334

3435
COVERAGE_SWITCH_REGEX = re.compile(r'.*\|.*\sswitch.*\(.*\)')
3536
COVERAGE_CASE_REGEX = re.compile(r'.*\|.*\scase.*:')
@@ -671,6 +672,93 @@ def load_python_json_coverage(json_file: str,
671672
return cp
672673

673674

675+
def load_go_coverage(target_dir: str,
676+
functions: Dict[str, function_profile.FunctionProfile],
677+
target_name: Optional[str] = None) -> CoverageProfile:
678+
"""Find and load fuzz.cov, a go coverage summary generated by the
679+
gocovmerge tool used in oss-fuzz.
680+
681+
Each line (except the first line) contains a coverage information in
682+
the following format:
683+
"<A>:<B>.<C>,<D>.<E> <F> <G>"
684+
A: Target file name
685+
B: Code range start line
686+
C: Code range start column
687+
D: Code range end line
688+
E: Code range end column
689+
F: Number of statements within the code range
690+
G: Total number of runtime coveage of this code range
691+
692+
For example, here is a sample fuzz.cov
693+
694+
mode: set
695+
test.go:18.67,20.27 2 1
696+
test.go:20.28,21.32 2 0
697+
698+
It means that from line 18 to line 21 in test.go,
699+
line 18 column 67 to line 20 column 27 has 1 hit count
700+
line 20 column 28 to line 21 column 32 has no hit count
701+
702+
return a Coverage Profile
703+
"""
704+
cp = CoverageProfile()
705+
cp.set_type('function')
706+
707+
# Retrieve all fuzz.cov coverage summary
708+
coverage_summary = utils.get_all_files_in_tree_with_regex(
709+
target_dir, 'fuzz.cov')
710+
logger.info(f"FOUND fuzz.cov COVERAGE FILES: {str(coverage_summary)}")
711+
712+
if len(coverage_summary) > 0:
713+
cov_file = coverage_summary[0]
714+
else:
715+
logger.info("Found no coverage files")
716+
return cp
717+
718+
# Extract the fuzz.cov coverage line information
719+
cp.coverage_files.append(cov_file)
720+
with open(cov_file, 'r') as f:
721+
cov_line = f.readlines()[1:]
722+
723+
# Process line coverage from fuzz.cov
724+
line_coverage: Dict[str, Dict[int, int]] = {}
725+
for line in cov_line:
726+
# Line format
727+
# <file>:<start_line>.<start_col>,<end_line>.<end_col> <stmts> <hit>
728+
file_name, data = line.split(':', 1)
729+
line_split = re.split('[:., ]', data)
730+
start_line = int(line_split[0])
731+
end_line = int(line_split[2])
732+
hit_count = int(line_split[5])
733+
734+
line_dict = line_coverage.get(file_name, {})
735+
for count in range(start_line, end_line + 1):
736+
hit_count = max(hit_count, line_dict.get(count, -1))
737+
line_dict[count] = hit_count
738+
739+
line_coverage[file_name] = line_dict
740+
741+
# Process coverage of each functions
742+
for function in functions.values():
743+
name = function.function_name
744+
start_line = int(function.function_linenumber)
745+
end_line = int(function.function_line_number_end)
746+
source_file = function.function_source_file.split('/source-files')[-1]
747+
748+
# Only process when we have correct start and end line number
749+
# for the function
750+
if start_line > 0 and end_line > 0:
751+
cp.covmap[name] = []
752+
for file, coverage in line_coverage.items():
753+
if file.endswith(source_file):
754+
for line_no in range(start_line, end_line + 1):
755+
cp.covmap[name].append(
756+
(line_no, coverage.get(line_no, 0)))
757+
758+
print(cp.covmap)
759+
return cp
760+
761+
674762
def load_jvm_coverage(target_dir: str,
675763
target_name: Optional[str] = None) -> CoverageProfile:
676764
"""Find and load jacoco.xml, a jvm xml coverage report file
@@ -774,7 +862,7 @@ def load_jvm_coverage(target_dir: str,
774862
# Store lines, hit_time into the covmap under the target method
775863
cp.covmap[name] = []
776864
# Add source code line and hitcount to coverage map of current function
777-
logger.debug(f"reading coverage: {name} -- {line_list[count]}")
865+
logger.debug(f"reading coverage: {name} -- {line_list}")
778866
for count in range(start_item, end_item):
779867
cp.covmap[name].append(line_list[count])
780868

src/fuzz_introspector/datatypes/fuzzer_profile.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def __init__(self,
7373
self.entrypoint_mod = frontend_yaml['ep']['module']
7474

7575
# Read entrypoint of fuzzer if this is a jvm module
76-
if target_lang == "jvm":
76+
if target_lang == "jvm" or target_lang == "go":
7777
self.entrypoint_method = frontend_yaml['Fuzzing method']
7878

7979
self._set_function_list(frontend_yaml)
@@ -107,6 +107,8 @@ def entrypoint_function(self):
107107
# macro and we manually considered it as
108108
# function in the frontend.
109109
return "fuzz_target"
110+
elif self.target_lang == "go":
111+
return self.entrypoint_method
110112
else:
111113
return None
112114

@@ -124,6 +126,13 @@ def identifier(self):
124126
# Class name is used for jvm identifier
125127
return os.path.basename(self.fuzzer_source_file)
126128

129+
elif self._target_lang == "rust":
130+
return os.path.basename(self.fuzzer_source_file).replace(".rs", "")
131+
132+
elif self._target_lang == "go":
133+
fuzzer_base_name = os.path.basename(self.fuzzer_source_file)
134+
return fuzzer_base_name.replace(".go", "").replace(".cgo", "")
135+
127136
return self.fuzzer_source_file
128137

129138
@property
@@ -149,6 +158,11 @@ def has_entry_point(self) -> bool:
149158
if name.startswith(self.entrypoint_function):
150159
return True
151160

161+
elif self.target_lang == "go":
162+
for name in self.all_class_functions:
163+
if name == self.entrypoint_function:
164+
return True
165+
152166
return False
153167

154168
def func_is_entrypoint(self, demangled_func_name: str) -> bool:
@@ -447,8 +461,8 @@ def _set_all_reached_functions(self) -> None:
447461
the fuzzer. This is based on identifying all functions reached by the
448462
fuzzer entrypoint function, e.g. LLVMFuzzerTestOneInput in C/C++.
449463
"""
450-
# Find C/CPP/Rust entry point
451-
if self._target_lang == "c-cpp" or self.target_lang == "rust":
464+
# Find C/CPP/Rust/Go entry point
465+
if self._target_lang == "c-cpp" or self.target_lang == "rust" or self.target_lang == "go":
452466
if self.entrypoint_function in self.all_class_functions:
453467
self.functions_reached_by_fuzzer = (self.all_class_functions[
454468
self.entrypoint_function].functions_reached)
@@ -511,6 +525,9 @@ def _load_coverage(self, target_folder: str) -> None:
511525
elif self.target_lang == "rust":
512526
self.coverage = code_coverage.load_llvm_coverage(
513527
target_folder, self.identifier, True)
528+
elif self.target_lang == "go":
529+
self.coverage = code_coverage.load_go_coverage(
530+
target_folder, self.all_class_functions)
514531
else:
515532
raise DataLoaderError(
516533
"The profile target has no coverage loading support")

src/fuzz_introspector/html_helpers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ def create_pfc_button(profiles: List[fuzzer_profile.FuzzerProfile],
183183
target_coverage_url += "/index.html"
184184
elif profile.target_lang == "rust":
185185
target_coverage_url += "/report.html"
186+
elif profile.target_lang == "go":
187+
target_coverage_url += "/index.html"
186188

187189
html_string += f"""
188190
<a href="{target_coverage_url}">
@@ -208,6 +210,8 @@ def html_get_table_of_contents(
208210
cov_index = "index.html"
209211
elif proj_profile.target_lang == "rust":
210212
cov_index = "report.html"
213+
elif proj_profile.target_lang == "go":
214+
cov_index = "index.html"
211215

212216
html_toc_string = ""
213217
html_toc_string += f"""<div class="left-sidebar">\

0 commit comments

Comments
 (0)