From afd242ca3310c8336e30a208ccdbfc589ade0633 Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Mon, 7 Nov 2022 19:01:42 -0800 Subject: [PATCH 1/4] feat: add slither-disable-start/end --- slither/core/slither_core.py | 58 ++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index 01af779164..f627fa1923 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -71,6 +71,12 @@ def __init__(self): self._show_ignored_findings = False + # Maps from file to detector name to the start/end ranges for that detector. + # Infinity is used to signal a detector has no end range. + self._ignore_ranges: defaultdict[str, defaultdict[str, List[(int,int)]]] = defaultdict(lambda: defaultdict(lambda: [])) + # Track which files for _ignore_ranges have been processed (a processed file may have no entries in _ignore_ranges). + self._processed_ignores: Set[str] = set() + self._compilation_units: List[SlitherCompilationUnit] = [] self._contracts: List[Contract] = [] @@ -286,7 +292,8 @@ def offset_to_definitions(self, filename_str: str, offset: int) -> Set[Source]: def has_ignore_comment(self, r: Dict) -> bool: """ - Check if the result has an ignore comment on the proceeding line, in which case, it is not valid + Check if the result has an ignore comment in the file or on the preceding line, in which + case, it is not valid """ if not self.crytic_compile: return False @@ -303,6 +310,53 @@ def has_ignore_comment(self, r: Dict) -> bool: ) for file, lines in mapping_elements_with_lines: + # The first time we check a file, find all start/end ignore comments and memoize them. + line_number = 1 + while True: + if file in self._processed_ignores: + break + + line_text = self.crytic_compile.get_code_from_line(file, line_number) + if line_text is None: + break + + start_regex = r"^\s*//\s*slither-disable-start\s*([a-zA-Z0-9_,-]*)" + end_regex = r"^\s*//\s*slither-disable-end\s*([a-zA-Z0-9_,-]*)" + start_match = re.findall(start_regex, line_text.decode("utf8")) + end_match = re.findall(end_regex, line_text.decode("utf8")) + + if start_match: + ignored = start_match[0].split(",") + if ignored: + for check in ignored: + vals = self._ignore_ranges[file][check] + if len(vals) == 0 or vals[-1][1] != float('inf'): + # First item in the array, or the prior item is fully populated. + self._ignore_ranges[file][check].append([line_number, float('inf')]) + else: + raise Exception("consecutive slither-disable-starts without slither-disable-end") + + if end_match: + ignored = end_match[0].split(",") + if ignored: + for check in ignored: + vals = self._ignore_ranges[file][check] + if len(vals) == 0 or vals[-1][1] != float('inf'): + raise Exception("slither-disable-end without slither-disable-start") + self._ignore_ranges[file][check][-1] = (vals[-1][0], line_number) + + line_number += 1 + + self._processed_ignores.add(file) + + # Check if result is within an ignored range. + ignore_ranges = self._ignore_ranges[file][r["check"]] + self._ignore_ranges[file]["all"] + for start, end in ignore_ranges: + # The full check must be within the ignore range to be ignored. + if start < lines[0] and end > lines[-1]: + return True + + # Check for next-line matchers. ignore_line_index = min(lines) - 1 ignore_line_text = self.crytic_compile.get_code_from_line(file, ignore_line_index) if ignore_line_text: @@ -324,7 +378,7 @@ def valid_result(self, r: Dict) -> bool: - All its source paths belong to the source path filtered - Or a similar result was reported and saved during a previous run - The --exclude-dependencies flag is set and results are only related to dependencies - - There is an ignore comment on the preceding line + - There is an ignore comment on the preceding line or in the file """ # Remove duplicate due to the multiple compilation support From 47b8cb8cd053e784ff8ea3f6f7f2a6644497ff4f Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Tue, 8 Nov 2022 13:13:15 -0800 Subject: [PATCH 2/4] refactor: pull out ignore comment parsing for pylint --- slither/core/slither_core.py | 79 +++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index f627fa1923..fc4fe9f42b 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -290,6 +290,46 @@ def offset_to_definitions(self, filename_str: str, offset: int) -> Set[Source]: ################################################################################### ################################################################################### + def parse_ignore_comments(self, file: str): + # The first time we check a file, find all start/end ignore comments and memoize them. + line_number = 1 + while True: + if file in self._processed_ignores: + break + + line_text = self.crytic_compile.get_code_from_line(file, line_number) + if line_text is None: + break + + start_regex = r"^\s*//\s*slither-disable-start\s*([a-zA-Z0-9_,-]*)" + end_regex = r"^\s*//\s*slither-disable-end\s*([a-zA-Z0-9_,-]*)" + start_match = re.findall(start_regex, line_text.decode("utf8")) + end_match = re.findall(end_regex, line_text.decode("utf8")) + + if start_match: + ignored = start_match[0].split(",") + if ignored: + for check in ignored: + vals = self._ignore_ranges[file][check] + if len(vals) == 0 or vals[-1][1] != float("inf"): + # First item in the array, or the prior item is fully populated. + self._ignore_ranges[file][check].append([line_number, float("inf")]) + else: + raise Exception("consecutive slither-disable-starts without slither-disable-end") + + if end_match: + ignored = end_match[0].split(",") + if ignored: + for check in ignored: + vals = self._ignore_ranges[file][check] + if len(vals) == 0 or vals[-1][1] != float("inf"): + raise Exception("slither-disable-end without slither-disable-start") + self._ignore_ranges[file][check][-1] = (vals[-1][0], line_number) + + line_number += 1 + + self._processed_ignores.add(file) + def has_ignore_comment(self, r: Dict) -> bool: """ Check if the result has an ignore comment in the file or on the preceding line, in which @@ -310,44 +350,7 @@ def has_ignore_comment(self, r: Dict) -> bool: ) for file, lines in mapping_elements_with_lines: - # The first time we check a file, find all start/end ignore comments and memoize them. - line_number = 1 - while True: - if file in self._processed_ignores: - break - - line_text = self.crytic_compile.get_code_from_line(file, line_number) - if line_text is None: - break - - start_regex = r"^\s*//\s*slither-disable-start\s*([a-zA-Z0-9_,-]*)" - end_regex = r"^\s*//\s*slither-disable-end\s*([a-zA-Z0-9_,-]*)" - start_match = re.findall(start_regex, line_text.decode("utf8")) - end_match = re.findall(end_regex, line_text.decode("utf8")) - - if start_match: - ignored = start_match[0].split(",") - if ignored: - for check in ignored: - vals = self._ignore_ranges[file][check] - if len(vals) == 0 or vals[-1][1] != float('inf'): - # First item in the array, or the prior item is fully populated. - self._ignore_ranges[file][check].append([line_number, float('inf')]) - else: - raise Exception("consecutive slither-disable-starts without slither-disable-end") - - if end_match: - ignored = end_match[0].split(",") - if ignored: - for check in ignored: - vals = self._ignore_ranges[file][check] - if len(vals) == 0 or vals[-1][1] != float('inf'): - raise Exception("slither-disable-end without slither-disable-start") - self._ignore_ranges[file][check][-1] = (vals[-1][0], line_number) - - line_number += 1 - - self._processed_ignores.add(file) + self.parse_ignore_comments(file) # Check if result is within an ignored range. ignore_ranges = self._ignore_ranges[file][r["check"]] + self._ignore_ranges[file]["all"] From 41086719fd4a5bdaec6b22b89e73dfe7af16146b Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Tue, 8 Nov 2022 13:13:37 -0800 Subject: [PATCH 3/4] style: run black --- slither/core/slither_core.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index fc4fe9f42b..76ebf92e8d 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -73,7 +73,9 @@ def __init__(self): # Maps from file to detector name to the start/end ranges for that detector. # Infinity is used to signal a detector has no end range. - self._ignore_ranges: defaultdict[str, defaultdict[str, List[(int,int)]]] = defaultdict(lambda: defaultdict(lambda: [])) + self._ignore_ranges: defaultdict[str, defaultdict[str, List[(int, int)]]] = defaultdict( + lambda: defaultdict(lambda: []) + ) # Track which files for _ignore_ranges have been processed (a processed file may have no entries in _ignore_ranges). self._processed_ignores: Set[str] = set() @@ -315,7 +317,9 @@ def parse_ignore_comments(self, file: str): # First item in the array, or the prior item is fully populated. self._ignore_ranges[file][check].append([line_number, float("inf")]) else: - raise Exception("consecutive slither-disable-starts without slither-disable-end") + raise Exception( + "consecutive slither-disable-starts without slither-disable-end" + ) if end_match: ignored = end_match[0].split(",") From 4608cef65100dbec5c93b86d5abd4c63152f5cac Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Tue, 8 Nov 2022 14:40:26 -0800 Subject: [PATCH 4/4] refactor: type fix, array to tuple --- slither/core/slither_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index 76ebf92e8d..eb5b40d0db 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -315,7 +315,7 @@ def parse_ignore_comments(self, file: str): vals = self._ignore_ranges[file][check] if len(vals) == 0 or vals[-1][1] != float("inf"): # First item in the array, or the prior item is fully populated. - self._ignore_ranges[file][check].append([line_number, float("inf")]) + self._ignore_ranges[file][check].append((line_number, float("inf"))) else: raise Exception( "consecutive slither-disable-starts without slither-disable-end"