slither.detectors.abstract_detector

  1import abc
  2import re
  3from logging import Logger
  4from typing import Optional, List, TYPE_CHECKING, Dict, Union, Callable
  5
  6from slither.core.compilation_unit import SlitherCompilationUnit, Language
  7from slither.core.declarations import Contract
  8from slither.formatters.exceptions import FormatImpossible
  9from slither.formatters.utils.patches import apply_patch, create_diff
 10from slither.utils.colors import green, yellow, red
 11from slither.utils.comparable_enum import ComparableEnum
 12from slither.utils.output import Output, SupportedOutput
 13
 14if TYPE_CHECKING:
 15    from slither import Slither
 16
 17
 18class IncorrectDetectorInitialization(Exception):
 19    pass
 20
 21
 22class DetectorClassification(ComparableEnum):
 23    HIGH = 0
 24    MEDIUM = 1
 25    LOW = 2
 26    INFORMATIONAL = 3
 27    OPTIMIZATION = 4
 28
 29    UNIMPLEMENTED = 999
 30
 31
 32classification_colors: Dict[DetectorClassification, Callable[[str], str]] = {
 33    DetectorClassification.INFORMATIONAL: green,
 34    DetectorClassification.OPTIMIZATION: green,
 35    DetectorClassification.LOW: green,
 36    DetectorClassification.MEDIUM: yellow,
 37    DetectorClassification.HIGH: red,
 38}
 39
 40classification_txt = {
 41    DetectorClassification.INFORMATIONAL: "Informational",
 42    DetectorClassification.OPTIMIZATION: "Optimization",
 43    DetectorClassification.LOW: "Low",
 44    DetectorClassification.MEDIUM: "Medium",
 45    DetectorClassification.HIGH: "High",
 46}
 47
 48
 49def make_solc_versions(minor: int, patch_min: int, patch_max: int) -> List[str]:
 50    """
 51    Create a list of solc version: [0.minor.patch_min .... 0.minor.patch_max]
 52    """
 53    return [f"0.{minor}.{x}" for x in range(patch_min, patch_max + 1)]
 54
 55
 56ALL_SOLC_VERSIONS_04 = make_solc_versions(4, 0, 26)
 57ALL_SOLC_VERSIONS_05 = make_solc_versions(5, 0, 17)
 58ALL_SOLC_VERSIONS_06 = make_solc_versions(6, 0, 12)
 59ALL_SOLC_VERSIONS_07 = make_solc_versions(7, 0, 6)
 60# No VERSIONS_08 as it is still in dev
 61
 62DETECTOR_INFO = List[Union[str, SupportedOutput]]
 63
 64
 65class AbstractDetector(metaclass=abc.ABCMeta):
 66    ARGUMENT = ""  # run the detector with slither.py --ARGUMENT
 67    HELP = ""  # help information
 68    IMPACT: DetectorClassification = DetectorClassification.UNIMPLEMENTED
 69    CONFIDENCE: DetectorClassification = DetectorClassification.UNIMPLEMENTED
 70
 71    WIKI = ""
 72
 73    WIKI_TITLE = ""
 74    WIKI_DESCRIPTION = ""
 75    WIKI_EXPLOIT_SCENARIO = ""
 76    WIKI_RECOMMENDATION = ""
 77
 78    STANDARD_JSON = True
 79
 80    # list of vulnerable solc versions as strings (e.g. ["0.4.25", "0.5.0"])
 81    # If the detector is meant to run on all versions, use None
 82    VULNERABLE_SOLC_VERSIONS: Optional[List[str]] = None
 83    # If the detector is meant to run on all languages, use None
 84    # Otherwise, use `solidity` or `vyper`
 85    LANGUAGE: Optional[str] = None
 86
 87    def __init__(
 88        self, compilation_unit: SlitherCompilationUnit, slither: "Slither", logger: Logger
 89    ) -> None:
 90        self.compilation_unit: SlitherCompilationUnit = compilation_unit
 91        self.contracts: List[Contract] = compilation_unit.contracts
 92        self.slither: "Slither" = slither
 93        # self.filename = slither.filename
 94        self.logger = logger
 95
 96        if not self.HELP:
 97            raise IncorrectDetectorInitialization(
 98                f"HELP is not initialized {self.__class__.__name__}"
 99            )
100
101        if not self.ARGUMENT:
102            raise IncorrectDetectorInitialization(
103                f"ARGUMENT is not initialized {self.__class__.__name__}"
104            )
105
106        if not self.WIKI:
107            raise IncorrectDetectorInitialization(
108                f"WIKI is not initialized {self.__class__.__name__}"
109            )
110
111        if not self.WIKI_TITLE:
112            raise IncorrectDetectorInitialization(
113                f"WIKI_TITLE is not initialized {self.__class__.__name__}"
114            )
115
116        if not self.WIKI_DESCRIPTION:
117            raise IncorrectDetectorInitialization(
118                f"WIKI_DESCRIPTION is not initialized {self.__class__.__name__}"
119            )
120
121        if not self.WIKI_EXPLOIT_SCENARIO and self.IMPACT not in [
122            DetectorClassification.INFORMATIONAL,
123            DetectorClassification.OPTIMIZATION,
124        ]:
125            raise IncorrectDetectorInitialization(
126                f"WIKI_EXPLOIT_SCENARIO is not initialized {self.__class__.__name__}"
127            )
128
129        if not self.WIKI_RECOMMENDATION:
130            raise IncorrectDetectorInitialization(
131                f"WIKI_RECOMMENDATION is not initialized {self.__class__.__name__}"
132            )
133
134        if self.VULNERABLE_SOLC_VERSIONS is not None and not self.VULNERABLE_SOLC_VERSIONS:
135            raise IncorrectDetectorInitialization(
136                f"VULNERABLE_SOLC_VERSIONS should not be an empty list {self.__class__.__name__}"
137            )
138
139        if self.LANGUAGE is not None and self.LANGUAGE not in [
140            Language.SOLIDITY.value,
141            Language.VYPER.value,
142        ]:
143            raise IncorrectDetectorInitialization(
144                f"LANGUAGE should not be either 'solidity' or 'vyper' {self.__class__.__name__}"
145            )
146
147        if re.match("^[a-zA-Z0-9_-]*$", self.ARGUMENT) is None:
148            raise IncorrectDetectorInitialization(
149                f"ARGUMENT has illegal character {self.__class__.__name__}"
150            )
151
152        if self.IMPACT not in [
153            DetectorClassification.LOW,
154            DetectorClassification.MEDIUM,
155            DetectorClassification.HIGH,
156            DetectorClassification.INFORMATIONAL,
157            DetectorClassification.OPTIMIZATION,
158        ]:
159            raise IncorrectDetectorInitialization(
160                f"IMPACT is not initialized {self.__class__.__name__}"
161            )
162
163        if self.CONFIDENCE not in [
164            DetectorClassification.LOW,
165            DetectorClassification.MEDIUM,
166            DetectorClassification.HIGH,
167            DetectorClassification.INFORMATIONAL,
168            DetectorClassification.OPTIMIZATION,
169        ]:
170            raise IncorrectDetectorInitialization(
171                f"CONFIDENCE is not initialized {self.__class__.__name__}"
172            )
173
174    def _log(self, info: str) -> None:
175        if self.logger:
176            self.logger.info(self.color(info))
177
178    def _is_applicable_detector(self) -> bool:
179        if self.VULNERABLE_SOLC_VERSIONS:
180            return (
181                self.compilation_unit.is_solidity
182                and self.compilation_unit.solc_version in self.VULNERABLE_SOLC_VERSIONS
183            )
184        if self.LANGUAGE:
185            return self.compilation_unit.language.value == self.LANGUAGE
186        return True
187
188    @abc.abstractmethod
189    def _detect(self) -> List[Output]:
190        """TODO Documentation"""
191        return []
192
193    # pylint: disable=too-many-branches
194    def detect(self) -> List[Dict]:
195        results: List[Dict] = []
196
197        # check solc version
198        if not self._is_applicable_detector():
199            return results
200
201        # only keep valid result, and remove duplicate
202        # Keep only dictionaries
203        for r in [output.data for output in self._detect()]:
204            if self.compilation_unit.core.valid_result(r) and r not in results:
205                results.append(r)
206        if results and self.logger:
207            self._log_result(results)
208        if self.compilation_unit.core.generate_patches:
209            for result in results:
210                try:
211                    self._format(self.compilation_unit, result)
212                    if not "patches" in result:
213                        continue
214                    result["patches_diff"] = {}
215                    for file in result["patches"]:
216                        original_txt = self.compilation_unit.core.source_code[file].encode("utf8")
217                        patched_txt = original_txt
218                        offset = 0
219                        patches = result["patches"][file]
220                        patches.sort(key=lambda x: x["start"])
221                        if not all(
222                            patches[i]["end"] <= patches[i + 1]["end"]
223                            for i in range(len(patches) - 1)
224                        ):
225                            self._log(
226                                f"Impossible to generate patch; patches collisions: {patches}"
227                            )
228                            continue
229                        for patch in patches:
230                            patched_txt, offset = apply_patch(patched_txt, patch, offset)
231                        diff = create_diff(self.compilation_unit, original_txt, patched_txt, file)
232                        if not diff:
233                            self._log(f"Impossible to generate patch; empty {result}")
234                        else:
235                            result["patches_diff"][file] = diff
236
237                except FormatImpossible as exception:
238                    self._log(f'\nImpossible to patch:\n\t{result["description"]}\t{exception}')
239
240        if results and self.slither.triage_mode:
241            while True:
242                indexes = input(
243                    f'Results to hide during next runs: "0,1,...,{len(results)}" or "All" (enter to not hide results):\n'
244                )
245                if indexes == "All":
246                    self.slither.save_results_to_hide(results)
247                    return []
248                if indexes == "":
249                    return results
250                if indexes.startswith("["):
251                    indexes = indexes[1:]
252                if indexes.endswith("]"):
253                    indexes = indexes[:-1]
254                try:
255                    indexes_converted = [int(i) for i in indexes.split(",")]
256                    self.slither.save_results_to_hide(
257                        [r for (idx, r) in enumerate(results) if idx in indexes_converted]
258                    )
259                    return [r for (idx, r) in enumerate(results) if idx not in indexes_converted]
260                except ValueError:
261                    self.logger.error(yellow("Malformed input. Example of valid input: 0,1,2,3"))
262        results = sorted(results, key=lambda x: x["id"])
263
264        return results
265
266    @property
267    def color(self) -> Callable[[str], str]:
268        return classification_colors[self.IMPACT]
269
270    def generate_result(
271        self,
272        info: DETECTOR_INFO,
273        additional_fields: Optional[Dict] = None,
274    ) -> Output:
275        output = Output(
276            info,
277            additional_fields,
278            standard_format=self.STANDARD_JSON,
279            markdown_root=self.slither.markdown_root,
280        )
281
282        output.data["check"] = self.ARGUMENT
283        output.data["impact"] = classification_txt[self.IMPACT]
284        output.data["confidence"] = classification_txt[self.CONFIDENCE]
285
286        return output
287
288    @staticmethod
289    def _format(_compilation_unit: SlitherCompilationUnit, _result: Dict) -> None:
290        """Implement format"""
291        return
292
293    def _log_result(self, results: List[Dict]) -> None:
294        info = "\n"
295        for idx, result in enumerate(results):
296            if self.slither.triage_mode:
297                info += f"{idx}: "
298            info += result["description"]
299        info += f"Reference: {self.WIKI}"
300        self._log(info)
class IncorrectDetectorInitialization(builtins.Exception):
19class IncorrectDetectorInitialization(Exception):
20    pass

Common base class for all non-exit exceptions.

Inherited Members
builtins.Exception
Exception
builtins.BaseException
with_traceback
args
class DetectorClassification(slither.utils.comparable_enum.ComparableEnum):
23class DetectorClassification(ComparableEnum):
24    HIGH = 0
25    MEDIUM = 1
26    LOW = 2
27    INFORMATIONAL = 3
28    OPTIMIZATION = 4
29
30    UNIMPLEMENTED = 999

An enumeration.

HIGH = 0
MEDIUM = 1
LOW = 2
INFORMATIONAL = 3
OPTIMIZATION = 4
UNIMPLEMENTED = 999
Inherited Members
enum.Enum
name
value
classification_colors: Dict[DetectorClassification, Callable[[str], str]] = {3: functools.partial(<function colorize>, '\x1b[92m'), 4: functools.partial(<function colorize>, '\x1b[92m'), 2: functools.partial(<function colorize>, '\x1b[92m'), 1: functools.partial(<function colorize>, '\x1b[93m'), 0: functools.partial(<function colorize>, '\x1b[91m')}
classification_txt = {3: 'Informational', 4: 'Optimization', 2: 'Low', 1: 'Medium', 0: 'High'}
def make_solc_versions(minor: int, patch_min: int, patch_max: int) -> List[str]:
50def make_solc_versions(minor: int, patch_min: int, patch_max: int) -> List[str]:
51    """
52    Create a list of solc version: [0.minor.patch_min .... 0.minor.patch_max]
53    """
54    return [f"0.{minor}.{x}" for x in range(patch_min, patch_max + 1)]

Create a list of solc version: [0.minor.patch_min .... 0.minor.patch_max]

ALL_SOLC_VERSIONS_04 = ['0.4.0', '0.4.1', '0.4.2', '0.4.3', '0.4.4', '0.4.5', '0.4.6', '0.4.7', '0.4.8', '0.4.9', '0.4.10', '0.4.11', '0.4.12', '0.4.13', '0.4.14', '0.4.15', '0.4.16', '0.4.17', '0.4.18', '0.4.19', '0.4.20', '0.4.21', '0.4.22', '0.4.23', '0.4.24', '0.4.25', '0.4.26']
ALL_SOLC_VERSIONS_05 = ['0.5.0', '0.5.1', '0.5.2', '0.5.3', '0.5.4', '0.5.5', '0.5.6', '0.5.7', '0.5.8', '0.5.9', '0.5.10', '0.5.11', '0.5.12', '0.5.13', '0.5.14', '0.5.15', '0.5.16', '0.5.17']
ALL_SOLC_VERSIONS_06 = ['0.6.0', '0.6.1', '0.6.2', '0.6.3', '0.6.4', '0.6.5', '0.6.6', '0.6.7', '0.6.8', '0.6.9', '0.6.10', '0.6.11', '0.6.12']
ALL_SOLC_VERSIONS_07 = ['0.7.0', '0.7.1', '0.7.2', '0.7.3', '0.7.4', '0.7.5', '0.7.6']
class AbstractDetector:
 66class AbstractDetector(metaclass=abc.ABCMeta):
 67    ARGUMENT = ""  # run the detector with slither.py --ARGUMENT
 68    HELP = ""  # help information
 69    IMPACT: DetectorClassification = DetectorClassification.UNIMPLEMENTED
 70    CONFIDENCE: DetectorClassification = DetectorClassification.UNIMPLEMENTED
 71
 72    WIKI = ""
 73
 74    WIKI_TITLE = ""
 75    WIKI_DESCRIPTION = ""
 76    WIKI_EXPLOIT_SCENARIO = ""
 77    WIKI_RECOMMENDATION = ""
 78
 79    STANDARD_JSON = True
 80
 81    # list of vulnerable solc versions as strings (e.g. ["0.4.25", "0.5.0"])
 82    # If the detector is meant to run on all versions, use None
 83    VULNERABLE_SOLC_VERSIONS: Optional[List[str]] = None
 84    # If the detector is meant to run on all languages, use None
 85    # Otherwise, use `solidity` or `vyper`
 86    LANGUAGE: Optional[str] = None
 87
 88    def __init__(
 89        self, compilation_unit: SlitherCompilationUnit, slither: "Slither", logger: Logger
 90    ) -> None:
 91        self.compilation_unit: SlitherCompilationUnit = compilation_unit
 92        self.contracts: List[Contract] = compilation_unit.contracts
 93        self.slither: "Slither" = slither
 94        # self.filename = slither.filename
 95        self.logger = logger
 96
 97        if not self.HELP:
 98            raise IncorrectDetectorInitialization(
 99                f"HELP is not initialized {self.__class__.__name__}"
100            )
101
102        if not self.ARGUMENT:
103            raise IncorrectDetectorInitialization(
104                f"ARGUMENT is not initialized {self.__class__.__name__}"
105            )
106
107        if not self.WIKI:
108            raise IncorrectDetectorInitialization(
109                f"WIKI is not initialized {self.__class__.__name__}"
110            )
111
112        if not self.WIKI_TITLE:
113            raise IncorrectDetectorInitialization(
114                f"WIKI_TITLE is not initialized {self.__class__.__name__}"
115            )
116
117        if not self.WIKI_DESCRIPTION:
118            raise IncorrectDetectorInitialization(
119                f"WIKI_DESCRIPTION is not initialized {self.__class__.__name__}"
120            )
121
122        if not self.WIKI_EXPLOIT_SCENARIO and self.IMPACT not in [
123            DetectorClassification.INFORMATIONAL,
124            DetectorClassification.OPTIMIZATION,
125        ]:
126            raise IncorrectDetectorInitialization(
127                f"WIKI_EXPLOIT_SCENARIO is not initialized {self.__class__.__name__}"
128            )
129
130        if not self.WIKI_RECOMMENDATION:
131            raise IncorrectDetectorInitialization(
132                f"WIKI_RECOMMENDATION is not initialized {self.__class__.__name__}"
133            )
134
135        if self.VULNERABLE_SOLC_VERSIONS is not None and not self.VULNERABLE_SOLC_VERSIONS:
136            raise IncorrectDetectorInitialization(
137                f"VULNERABLE_SOLC_VERSIONS should not be an empty list {self.__class__.__name__}"
138            )
139
140        if self.LANGUAGE is not None and self.LANGUAGE not in [
141            Language.SOLIDITY.value,
142            Language.VYPER.value,
143        ]:
144            raise IncorrectDetectorInitialization(
145                f"LANGUAGE should not be either 'solidity' or 'vyper' {self.__class__.__name__}"
146            )
147
148        if re.match("^[a-zA-Z0-9_-]*$", self.ARGUMENT) is None:
149            raise IncorrectDetectorInitialization(
150                f"ARGUMENT has illegal character {self.__class__.__name__}"
151            )
152
153        if self.IMPACT not in [
154            DetectorClassification.LOW,
155            DetectorClassification.MEDIUM,
156            DetectorClassification.HIGH,
157            DetectorClassification.INFORMATIONAL,
158            DetectorClassification.OPTIMIZATION,
159        ]:
160            raise IncorrectDetectorInitialization(
161                f"IMPACT is not initialized {self.__class__.__name__}"
162            )
163
164        if self.CONFIDENCE not in [
165            DetectorClassification.LOW,
166            DetectorClassification.MEDIUM,
167            DetectorClassification.HIGH,
168            DetectorClassification.INFORMATIONAL,
169            DetectorClassification.OPTIMIZATION,
170        ]:
171            raise IncorrectDetectorInitialization(
172                f"CONFIDENCE is not initialized {self.__class__.__name__}"
173            )
174
175    def _log(self, info: str) -> None:
176        if self.logger:
177            self.logger.info(self.color(info))
178
179    def _is_applicable_detector(self) -> bool:
180        if self.VULNERABLE_SOLC_VERSIONS:
181            return (
182                self.compilation_unit.is_solidity
183                and self.compilation_unit.solc_version in self.VULNERABLE_SOLC_VERSIONS
184            )
185        if self.LANGUAGE:
186            return self.compilation_unit.language.value == self.LANGUAGE
187        return True
188
189    @abc.abstractmethod
190    def _detect(self) -> List[Output]:
191        """TODO Documentation"""
192        return []
193
194    # pylint: disable=too-many-branches
195    def detect(self) -> List[Dict]:
196        results: List[Dict] = []
197
198        # check solc version
199        if not self._is_applicable_detector():
200            return results
201
202        # only keep valid result, and remove duplicate
203        # Keep only dictionaries
204        for r in [output.data for output in self._detect()]:
205            if self.compilation_unit.core.valid_result(r) and r not in results:
206                results.append(r)
207        if results and self.logger:
208            self._log_result(results)
209        if self.compilation_unit.core.generate_patches:
210            for result in results:
211                try:
212                    self._format(self.compilation_unit, result)
213                    if not "patches" in result:
214                        continue
215                    result["patches_diff"] = {}
216                    for file in result["patches"]:
217                        original_txt = self.compilation_unit.core.source_code[file].encode("utf8")
218                        patched_txt = original_txt
219                        offset = 0
220                        patches = result["patches"][file]
221                        patches.sort(key=lambda x: x["start"])
222                        if not all(
223                            patches[i]["end"] <= patches[i + 1]["end"]
224                            for i in range(len(patches) - 1)
225                        ):
226                            self._log(
227                                f"Impossible to generate patch; patches collisions: {patches}"
228                            )
229                            continue
230                        for patch in patches:
231                            patched_txt, offset = apply_patch(patched_txt, patch, offset)
232                        diff = create_diff(self.compilation_unit, original_txt, patched_txt, file)
233                        if not diff:
234                            self._log(f"Impossible to generate patch; empty {result}")
235                        else:
236                            result["patches_diff"][file] = diff
237
238                except FormatImpossible as exception:
239                    self._log(f'\nImpossible to patch:\n\t{result["description"]}\t{exception}')
240
241        if results and self.slither.triage_mode:
242            while True:
243                indexes = input(
244                    f'Results to hide during next runs: "0,1,...,{len(results)}" or "All" (enter to not hide results):\n'
245                )
246                if indexes == "All":
247                    self.slither.save_results_to_hide(results)
248                    return []
249                if indexes == "":
250                    return results
251                if indexes.startswith("["):
252                    indexes = indexes[1:]
253                if indexes.endswith("]"):
254                    indexes = indexes[:-1]
255                try:
256                    indexes_converted = [int(i) for i in indexes.split(",")]
257                    self.slither.save_results_to_hide(
258                        [r for (idx, r) in enumerate(results) if idx in indexes_converted]
259                    )
260                    return [r for (idx, r) in enumerate(results) if idx not in indexes_converted]
261                except ValueError:
262                    self.logger.error(yellow("Malformed input. Example of valid input: 0,1,2,3"))
263        results = sorted(results, key=lambda x: x["id"])
264
265        return results
266
267    @property
268    def color(self) -> Callable[[str], str]:
269        return classification_colors[self.IMPACT]
270
271    def generate_result(
272        self,
273        info: DETECTOR_INFO,
274        additional_fields: Optional[Dict] = None,
275    ) -> Output:
276        output = Output(
277            info,
278            additional_fields,
279            standard_format=self.STANDARD_JSON,
280            markdown_root=self.slither.markdown_root,
281        )
282
283        output.data["check"] = self.ARGUMENT
284        output.data["impact"] = classification_txt[self.IMPACT]
285        output.data["confidence"] = classification_txt[self.CONFIDENCE]
286
287        return output
288
289    @staticmethod
290    def _format(_compilation_unit: SlitherCompilationUnit, _result: Dict) -> None:
291        """Implement format"""
292        return
293
294    def _log_result(self, results: List[Dict]) -> None:
295        info = "\n"
296        for idx, result in enumerate(results):
297            if self.slither.triage_mode:
298                info += f"{idx}: "
299            info += result["description"]
300        info += f"Reference: {self.WIKI}"
301        self._log(info)
ARGUMENT = ''
HELP = ''
IMPACT: DetectorClassification = 999
CONFIDENCE: DetectorClassification = 999
WIKI = ''
WIKI_TITLE = ''
WIKI_DESCRIPTION = ''
WIKI_EXPLOIT_SCENARIO = ''
WIKI_RECOMMENDATION = ''
STANDARD_JSON = True
VULNERABLE_SOLC_VERSIONS: Union[List[str], NoneType] = None
LANGUAGE: Union[str, NoneType] = None
logger
def detect(self) -> List[Dict]:
195    def detect(self) -> List[Dict]:
196        results: List[Dict] = []
197
198        # check solc version
199        if not self._is_applicable_detector():
200            return results
201
202        # only keep valid result, and remove duplicate
203        # Keep only dictionaries
204        for r in [output.data for output in self._detect()]:
205            if self.compilation_unit.core.valid_result(r) and r not in results:
206                results.append(r)
207        if results and self.logger:
208            self._log_result(results)
209        if self.compilation_unit.core.generate_patches:
210            for result in results:
211                try:
212                    self._format(self.compilation_unit, result)
213                    if not "patches" in result:
214                        continue
215                    result["patches_diff"] = {}
216                    for file in result["patches"]:
217                        original_txt = self.compilation_unit.core.source_code[file].encode("utf8")
218                        patched_txt = original_txt
219                        offset = 0
220                        patches = result["patches"][file]
221                        patches.sort(key=lambda x: x["start"])
222                        if not all(
223                            patches[i]["end"] <= patches[i + 1]["end"]
224                            for i in range(len(patches) - 1)
225                        ):
226                            self._log(
227                                f"Impossible to generate patch; patches collisions: {patches}"
228                            )
229                            continue
230                        for patch in patches:
231                            patched_txt, offset = apply_patch(patched_txt, patch, offset)
232                        diff = create_diff(self.compilation_unit, original_txt, patched_txt, file)
233                        if not diff:
234                            self._log(f"Impossible to generate patch; empty {result}")
235                        else:
236                            result["patches_diff"][file] = diff
237
238                except FormatImpossible as exception:
239                    self._log(f'\nImpossible to patch:\n\t{result["description"]}\t{exception}')
240
241        if results and self.slither.triage_mode:
242            while True:
243                indexes = input(
244                    f'Results to hide during next runs: "0,1,...,{len(results)}" or "All" (enter to not hide results):\n'
245                )
246                if indexes == "All":
247                    self.slither.save_results_to_hide(results)
248                    return []
249                if indexes == "":
250                    return results
251                if indexes.startswith("["):
252                    indexes = indexes[1:]
253                if indexes.endswith("]"):
254                    indexes = indexes[:-1]
255                try:
256                    indexes_converted = [int(i) for i in indexes.split(",")]
257                    self.slither.save_results_to_hide(
258                        [r for (idx, r) in enumerate(results) if idx in indexes_converted]
259                    )
260                    return [r for (idx, r) in enumerate(results) if idx not in indexes_converted]
261                except ValueError:
262                    self.logger.error(yellow("Malformed input. Example of valid input: 0,1,2,3"))
263        results = sorted(results, key=lambda x: x["id"])
264
265        return results
color: Callable[[str], str]
267    @property
268    def color(self) -> Callable[[str], str]:
269        return classification_colors[self.IMPACT]
271    def generate_result(
272        self,
273        info: DETECTOR_INFO,
274        additional_fields: Optional[Dict] = None,
275    ) -> Output:
276        output = Output(
277            info,
278            additional_fields,
279            standard_format=self.STANDARD_JSON,
280            markdown_root=self.slither.markdown_root,
281        )
282
283        output.data["check"] = self.ARGUMENT
284        output.data["impact"] = classification_txt[self.IMPACT]
285        output.data["confidence"] = classification_txt[self.CONFIDENCE]
286
287        return output