slither.utils.output

  1import hashlib
  2import json
  3import logging
  4import os
  5import zipfile
  6from collections import OrderedDict
  7from importlib import metadata
  8from typing import Tuple, Optional, Dict, List, Union, Any, TYPE_CHECKING, Type
  9from zipfile import ZipFile
 10
 11
 12from slither.core.cfg.node import Node
 13from slither.core.declarations import (
 14    Contract,
 15    Function,
 16    Enum,
 17    Event,
 18    Structure,
 19    Pragma,
 20    FunctionContract,
 21    CustomError,
 22)
 23from slither.core.source_mapping.source_mapping import SourceMapping
 24from slither.core.variables.local_variable import LocalVariable
 25from slither.core.variables.variable import Variable
 26from slither.exceptions import SlitherError
 27from slither.utils.colors import yellow
 28from slither.utils.myprettytable import MyPrettyTable
 29
 30if TYPE_CHECKING:
 31    from slither.core.compilation_unit import SlitherCompilationUnit
 32    from slither.detectors.abstract_detector import AbstractDetector
 33
 34logger = logging.getLogger("Slither")
 35
 36###################################################################################
 37###################################################################################
 38# region Output
 39###################################################################################
 40###################################################################################
 41
 42
 43def output_to_json(filename: Optional[str], error, results: Dict) -> None:
 44    """
 45
 46    :param filename: Filename where the json will be written. If None or "-", write to stdout
 47    :param error: Error to report
 48    :param results: Results to report
 49    :param logger: Logger where to log potential info
 50    :return:
 51    """
 52    # Create our encapsulated JSON result.
 53    json_result = {"success": error is None, "error": error, "results": results}
 54
 55    if filename == "-":
 56        filename = None
 57
 58    # Determine if we should output to stdout
 59    if filename is None:
 60        # Write json to console
 61        print(json.dumps(json_result))
 62    else:
 63        # Write json to file
 64        if os.path.isfile(filename):
 65            logger.info(yellow(f"{filename} exists already, the overwrite is prevented"))
 66        else:
 67            with open(filename, "w", encoding="utf8") as f:
 68                json.dump(json_result, f, indent=2)
 69
 70
 71def _output_result_to_sarif(
 72    detector: Dict, detectors_classes: List["AbstractDetector"], sarif: Dict
 73) -> None:
 74    confidence = "very-high"
 75    if detector["confidence"] == "Medium":
 76        confidence = "high"
 77    elif detector["confidence"] == "Low":
 78        confidence = "medium"
 79    elif detector["confidence"] == "Informational":
 80        confidence = "low"
 81
 82    risk = "0.0"
 83    if detector["impact"] == "High":
 84        risk = "8.0"
 85    elif detector["impact"] == "Medium":
 86        risk = "4.0"
 87    elif detector["impact"] == "Low":
 88        risk = "3.0"
 89
 90    detector_class = next((d for d in detectors_classes if d.ARGUMENT == detector["check"]))
 91    check_id = (
 92        str(detector_class.IMPACT.value)
 93        + "-"
 94        + str(detector_class.CONFIDENCE.value)
 95        + "-"
 96        + detector["check"]
 97    )
 98
 99    rule = {
100        "id": check_id,
101        "name": detector["check"],
102        "properties": {"precision": confidence, "security-severity": risk},
103        "shortDescription": {"text": detector_class.WIKI_TITLE},
104        "help": {"text": detector_class.WIKI_RECOMMENDATION},
105    }
106    # Add the rule if does not exist yet
107    if len([x for x in sarif["runs"][0]["tool"]["driver"]["rules"] if x["id"] == check_id]) == 0:
108        sarif["runs"][0]["tool"]["driver"]["rules"].append(rule)
109
110    if not detector["elements"]:
111        logger.info(yellow("Cannot generate Github security alert for finding without location"))
112        logger.info(yellow(detector["description"]))
113        logger.info(yellow("This will be supported in a future Slither release"))
114        return
115
116    # From 3.19.10 (http://docs.oasis-open.org/sarif/sarif/v2.0/csprd01/sarif-v2.0-csprd01.html)
117    # The locations array SHALL NOT contain more than one element unless the condition indicated by the result,
118    # if any, can only be corrected by making a change at every location specified in the array.
119    finding = detector["elements"][0]
120    path = finding["source_mapping"]["filename_relative"]
121    start_line = finding["source_mapping"]["lines"][0]
122    end_line = finding["source_mapping"]["lines"][-1]
123
124    sarif["runs"][0]["results"].append(
125        {
126            "ruleId": check_id,
127            "message": {"text": detector["description"], "markdown": detector["markdown"]},
128            "level": "warning",
129            "locations": [
130                {
131                    "physicalLocation": {
132                        "artifactLocation": {"uri": path},
133                        "region": {"startLine": start_line, "endLine": end_line},
134                    }
135                }
136            ],
137            "partialFingerprints": {"id": detector["id"]},
138        }
139    )
140
141
142def output_to_sarif(
143    filename: Optional[str], results: Dict, detectors_classes: List[Type["AbstractDetector"]]
144) -> None:
145    """
146
147    :param filename:
148    :type filename:
149    :param results:
150    :type results:
151    :return:
152    :rtype:
153    """
154
155    sarif: Dict[str, Any] = {
156        "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
157        "version": "2.1.0",
158        "runs": [
159            {
160                "tool": {
161                    "driver": {
162                        "name": "Slither",
163                        "informationUri": "https://github.com/crytic/slither",
164                        "version": metadata.version("slither-analyzer"),
165                        "rules": [],
166                    }
167                },
168                "results": [],
169            }
170        ],
171    }
172
173    for detector in results.get("detectors", []):
174        _output_result_to_sarif(detector, detectors_classes, sarif)
175
176    if filename == "-":
177        filename = None
178
179    # Determine if we should output to stdout
180    if filename is None:
181        # Write json to console
182        print(json.dumps(sarif))
183    else:
184        # Write json to file
185        if os.path.isfile(filename):
186            logger.info(yellow(f"{filename} exists already, the overwrite is prevented"))
187        else:
188            with open(filename, "w", encoding="utf8") as f:
189                json.dump(sarif, f, indent=2)
190
191
192# https://docs.python.org/3/library/zipfile.html#zipfile-objects
193ZIP_TYPES_ACCEPTED = {
194    "lzma": zipfile.ZIP_LZMA,
195    "stored": zipfile.ZIP_STORED,
196    "deflated": zipfile.ZIP_DEFLATED,
197    "bzip2": zipfile.ZIP_BZIP2,
198}
199
200
201def output_to_zip(filename: str, error: Optional[str], results: Dict, zip_type: str = "lzma"):
202    """
203    Output the results to a zip
204    The file in the zip is named slither_results.json
205    Note: the json file will not have indentation, as a result the resulting json file will be smaller
206    :param zip_type:
207    :param filename:
208    :param error:
209    :param results:
210    :return:
211    """
212    json_result = {"success": error is None, "error": error, "results": results}
213    if os.path.isfile(filename):
214        logger.info(yellow(f"{filename} exists already, the overwrite is prevented"))
215    else:
216        with ZipFile(
217            filename,
218            "w",
219            compression=ZIP_TYPES_ACCEPTED.get(zip_type, zipfile.ZIP_LZMA),
220        ) as file_desc:
221            file_desc.writestr("slither_results.json", json.dumps(json_result).encode("utf8"))
222
223
224# endregion
225###################################################################################
226###################################################################################
227# region Json generation
228###################################################################################
229###################################################################################
230
231
232def _convert_to_description(d: str) -> str:
233    if isinstance(d, str):
234        return d
235
236    if not isinstance(d, SourceMapping):
237        raise SlitherError(f"{d} does not inherit from SourceMapping, conversion impossible")
238
239    if isinstance(d, Node):
240        if d.expression:
241            return f"{d.expression} ({d.source_mapping})"
242        return f"{str(d)} ({d.source_mapping})"
243
244    if hasattr(d, "canonical_name"):
245        return f"{d.canonical_name} ({d.source_mapping})"
246
247    if hasattr(d, "name"):
248        return f"{d.name} ({d.source_mapping})"
249
250    raise SlitherError(f"{type(d)} cannot be converted (no name, or canonical_name")
251
252
253def _convert_to_markdown(d: str, markdown_root: str) -> str:
254    if isinstance(d, str):
255        return d
256
257    if not isinstance(d, SourceMapping):
258        raise SlitherError(f"{d} does not inherit from SourceMapping, conversion impossible")
259
260    if isinstance(d, Node):
261        if d.expression:
262            return f"[{d.expression}]({d.source_mapping.to_markdown(markdown_root)})"
263        return f"[{str(d)}]({d.source_mapping.to_markdown(markdown_root)})"
264
265    if hasattr(d, "canonical_name"):
266        return f"[{d.canonical_name}]({d.source_mapping.to_markdown(markdown_root)})"
267
268    if hasattr(d, "name"):
269        return f"[{d.name}]({d.source_mapping.to_markdown(markdown_root)})"
270
271    raise SlitherError(f"{type(d)} cannot be converted (no name, or canonical_name")
272
273
274def _convert_to_id(d: str) -> str:
275    """
276    Id keeps the source mapping of the node, otherwise we risk to consider two different node as the same
277    :param d:
278    :return:
279    """
280    if isinstance(d, str):
281        return d
282
283    if not isinstance(d, SourceMapping):
284        raise SlitherError(f"{d} does not inherit from SourceMapping, conversion impossible")
285
286    if isinstance(d, Node):
287        if d.expression:
288            return f"{d.expression} ({d.source_mapping})"
289        return f"{str(d)} ({d.source_mapping})"
290
291    if isinstance(d, Pragma):
292        return f"{d} ({d.source_mapping})"
293
294    if hasattr(d, "canonical_name"):
295        return f"{d.canonical_name}"
296
297    if hasattr(d, "name"):
298        return f"{d.name}"
299
300    raise SlitherError(f"{type(d)} cannot be converted (no name, or canonical_name")
301
302
303# endregion
304###################################################################################
305###################################################################################
306# region Internal functions
307###################################################################################
308###################################################################################
309
310
311def _create_base_element(
312    custom_type: str,
313    name: str,
314    source_mapping: Dict,
315    type_specific_fields: Optional[
316        Dict[
317            str,
318            Union[
319                Dict[
320                    str,
321                    Union[
322                        str,
323                        Dict[str, Union[int, str, bool, List[int]]],
324                        Dict[
325                            str,
326                            Union[
327                                Dict[str, Union[str, Dict[str, Union[int, str, bool, List[int]]]]],
328                                str,
329                            ],
330                        ],
331                    ],
332                ],
333                Dict[str, Union[str, Dict[str, Union[int, str, bool, List[int]]]]],
334                str,
335                List[str],
336            ],
337        ]
338    ] = None,
339    additional_fields: Optional[Dict[str, str]] = None,
340) -> Dict[str, Any]:
341    if additional_fields is None:
342        additional_fields = {}
343    if type_specific_fields is None:
344        type_specific_fields = {}
345    element = {"type": custom_type, "name": name, "source_mapping": source_mapping}
346    if type_specific_fields:
347        element["type_specific_fields"] = type_specific_fields
348    if additional_fields:
349        element["additional_fields"] = additional_fields
350    return element
351
352
353def _create_parent_element(
354    element: SourceMapping,
355) -> Dict[
356    str,
357    Union[
358        str,
359        Dict[str, Union[int, str, bool, List[int]]],
360        Dict[str, Union[Dict[str, Union[str, Dict[str, Union[int, str, bool, List[int]]]]], str]],
361    ],
362]:
363    # pylint: disable=import-outside-toplevel
364    from slither.core.declarations.contract_level import ContractLevel
365
366    if isinstance(element, FunctionContract):
367        if element.contract_declarer:
368            contract = Output("")
369            contract.add_contract(element.contract_declarer)
370            return contract.data["elements"][0]
371    elif isinstance(element, ContractLevel):
372        if element.contract:
373            contract = Output("")
374            contract.add_contract(element.contract)
375            return contract.data["elements"][0]
376    elif isinstance(element, (LocalVariable, Node)):
377        if element.function:
378            function = Output("")
379            function.add_function(element.function)
380            return function.data["elements"][0]
381    return None
382
383
384SupportedOutput = Union[Variable, Contract, Function, Enum, Event, Structure, Pragma, Node]
385AllSupportedOutput = Union[str, SupportedOutput]
386
387
388class Output:
389    def __init__(
390        self,
391        info_: Union[str, List[Union[str, SupportedOutput]]],
392        additional_fields: Optional[Dict] = None,
393        markdown_root: str = "",
394        standard_format: bool = True,
395    ) -> None:
396        if additional_fields is None:
397            additional_fields = {}
398
399        # Allow info to be a string to simplify the API
400        info: List[Union[str, SupportedOutput]]
401        if isinstance(info_, str):
402            info = [info_]
403        else:
404            info = info_
405
406        self._data = OrderedDict()
407        self._data["elements"] = []
408        self._data["description"] = "".join(_convert_to_description(d) for d in info)
409        self._data["markdown"] = "".join(_convert_to_markdown(d, markdown_root) for d in info)
410        self._data["first_markdown_element"] = ""
411        self._markdown_root = markdown_root
412
413        id_txt = "".join(_convert_to_id(d) for d in info)
414        self._data["id"] = hashlib.sha3_256(id_txt.encode("utf-8")).hexdigest()
415
416        if standard_format:
417            to_add = [i for i in info if not isinstance(i, str)]
418
419            for add in to_add:
420                self.add(add)
421
422        if additional_fields:
423            self._data["additional_fields"] = additional_fields
424
425    def add(self, add: SupportedOutput, additional_fields: Optional[Dict] = None) -> None:
426        if not self._data["first_markdown_element"]:
427            self._data["first_markdown_element"] = add.source_mapping.to_markdown(
428                self._markdown_root
429            )
430        if isinstance(add, Variable):
431            self.add_variable(add, additional_fields=additional_fields)
432        elif isinstance(add, Contract):
433            self.add_contract(add, additional_fields=additional_fields)
434        elif isinstance(add, Function):
435            self.add_function(add, additional_fields=additional_fields)
436        elif isinstance(add, Enum):
437            self.add_enum(add, additional_fields=additional_fields)
438        elif isinstance(add, Event):
439            self.add_event(add, additional_fields=additional_fields)
440        elif isinstance(add, Structure):
441            self.add_struct(add, additional_fields=additional_fields)
442        elif isinstance(add, CustomError):
443            self.add_custom_error(add, additional_fields=additional_fields)
444        elif isinstance(add, Pragma):
445            self.add_pragma(add, additional_fields=additional_fields)
446        elif isinstance(add, Node):
447            self.add_node(add, additional_fields=additional_fields)
448        else:
449            raise SlitherError(f"Impossible to add {type(add)} to the json")
450
451    @property
452    def data(self) -> Dict:
453        return self._data
454
455    @property
456    def elements(self) -> List[Dict]:
457        return self._data["elements"]
458
459    # endregion
460    ###################################################################################
461    ###################################################################################
462    # region Variables
463    ###################################################################################
464    ###################################################################################
465
466    def add_variable(self, variable: Variable, additional_fields: Optional[Dict] = None) -> None:
467        if additional_fields is None:
468            additional_fields = {}
469        type_specific_fields = {"parent": _create_parent_element(variable)}
470        element = _create_base_element(
471            "variable",
472            variable.name,
473            variable.source_mapping.to_json(),
474            type_specific_fields,
475            additional_fields,
476        )
477        self._data["elements"].append(element)
478
479    def add_variables(self, variables: List[Variable]):
480        for variable in sorted(variables, key=lambda x: x.name):
481            self.add_variable(variable)
482
483    # endregion
484    ###################################################################################
485    ###################################################################################
486    # region Contract
487    ###################################################################################
488    ###################################################################################
489
490    def add_contract(self, contract: Contract, additional_fields: Optional[Dict] = None) -> None:
491        if additional_fields is None:
492            additional_fields = {}
493        element = _create_base_element(
494            "contract", contract.name, contract.source_mapping.to_json(), {}, additional_fields
495        )
496        self._data["elements"].append(element)
497
498    # endregion
499    ###################################################################################
500    ###################################################################################
501    # region Functions
502    ###################################################################################
503    ###################################################################################
504
505    def add_function(self, function: Function, additional_fields: Optional[Dict] = None) -> None:
506        if additional_fields is None:
507            additional_fields = {}
508        type_specific_fields = {
509            "parent": _create_parent_element(function),
510            "signature": function.full_name,
511        }
512        element = _create_base_element(
513            "function",
514            function.name,
515            function.source_mapping.to_json(),
516            type_specific_fields,
517            additional_fields,
518        )
519        self._data["elements"].append(element)
520
521    def add_functions(self, functions: List[Function], additional_fields: Optional[Dict] = None):
522        if additional_fields is None:
523            additional_fields = {}
524        for function in sorted(functions, key=lambda x: x.name):
525            self.add_function(function, additional_fields)
526
527    # endregion
528    ###################################################################################
529    ###################################################################################
530    # region Enum
531    ###################################################################################
532    ###################################################################################
533
534    def add_enum(self, enum: Enum, additional_fields: Optional[Dict] = None) -> None:
535        if additional_fields is None:
536            additional_fields = {}
537        type_specific_fields = {"parent": _create_parent_element(enum)}
538        element = _create_base_element(
539            "enum",
540            enum.name,
541            enum.source_mapping.to_json(),
542            type_specific_fields,
543            additional_fields,
544        )
545        self._data["elements"].append(element)
546
547    # endregion
548    ###################################################################################
549    ###################################################################################
550    # region Structures
551    ###################################################################################
552    ###################################################################################
553
554    def add_struct(self, struct: Structure, additional_fields: Optional[Dict] = None) -> None:
555        if additional_fields is None:
556            additional_fields = {}
557        type_specific_fields = {"parent": _create_parent_element(struct)}
558        element = _create_base_element(
559            "struct",
560            struct.name,
561            struct.source_mapping.to_json(),
562            type_specific_fields,
563            additional_fields,
564        )
565        self._data["elements"].append(element)
566
567    # endregion
568    ###################################################################################
569    ###################################################################################
570    # region Events
571    ###################################################################################
572    ###################################################################################
573
574    def add_event(self, event: Event, additional_fields: Optional[Dict] = None) -> None:
575        if additional_fields is None:
576            additional_fields = {}
577        type_specific_fields = {
578            "parent": _create_parent_element(event),
579            "signature": event.full_name,
580        }
581        element = _create_base_element(
582            "event",
583            event.name,
584            event.source_mapping.to_json(),
585            type_specific_fields,
586            additional_fields,
587        )
588
589        self._data["elements"].append(element)
590
591    # endregion
592    ###################################################################################
593    ###################################################################################
594    # region CustomError
595    ###################################################################################
596    ###################################################################################
597
598    def add_custom_error(
599        self, custom_error: CustomError, additional_fields: Optional[Dict] = None
600    ) -> None:
601        if additional_fields is None:
602            additional_fields = {}
603        type_specific_fields = {
604            "parent": _create_parent_element(custom_error),
605            "signature": custom_error.full_name,
606        }
607        element = _create_base_element(
608            "custom_error",
609            custom_error.name,
610            custom_error.source_mapping.to_json(),
611            type_specific_fields,
612            additional_fields,
613        )
614
615        self._data["elements"].append(element)
616
617    # endregion
618    ###################################################################################
619    ###################################################################################
620    # region Nodes
621    ###################################################################################
622    ###################################################################################
623
624    def add_node(self, node: Node, additional_fields: Optional[Dict] = None) -> None:
625        if additional_fields is None:
626            additional_fields = {}
627        type_specific_fields = {
628            "parent": _create_parent_element(node),
629        }
630        node_name = str(node.expression) if node.expression else ""
631        element = _create_base_element(
632            "node",
633            node_name,
634            node.source_mapping.to_json(),
635            type_specific_fields,
636            additional_fields,
637        )
638        self._data["elements"].append(element)
639
640    def add_nodes(self, nodes: List[Node]):
641        for node in sorted(nodes, key=lambda x: x.node_id):
642            self.add_node(node)
643
644    # endregion
645    ###################################################################################
646    ###################################################################################
647    # region Pragma
648    ###################################################################################
649    ###################################################################################
650
651    def add_pragma(self, pragma: Pragma, additional_fields: Optional[Dict] = None) -> None:
652        if additional_fields is None:
653            additional_fields = {}
654        type_specific_fields = {"directive": pragma.directive}
655        element = _create_base_element(
656            "pragma",
657            pragma.version,
658            pragma.source_mapping.to_json(),
659            type_specific_fields,
660            additional_fields,
661        )
662        self._data["elements"].append(element)
663
664    # endregion
665    ###################################################################################
666    ###################################################################################
667    # region File
668    ###################################################################################
669    ###################################################################################
670
671    def add_file(self, filename: str, content: str, additional_fields: Optional[Dict] = None):
672        if additional_fields is None:
673            additional_fields = {}
674        type_specific_fields = {"filename": filename, "content": content}
675        element = _create_base_element("file", type_specific_fields, additional_fields)
676
677        self._data["elements"].append(element)
678
679    # endregion
680    ###################################################################################
681    ###################################################################################
682    # region Pretty Table
683    ###################################################################################
684    ###################################################################################
685
686    def add_pretty_table(
687        self,
688        content: MyPrettyTable,
689        name: str,
690        additional_fields: Optional[Dict] = None,
691    ):
692        if additional_fields is None:
693            additional_fields = {}
694        type_specific_fields = {"content": content.to_json(), "name": name}
695        element = _create_base_element("pretty_table", type_specific_fields, additional_fields)
696
697        self._data["elements"].append(element)
698
699    # endregion
700    ###################################################################################
701    ###################################################################################
702    # region Others
703    ###################################################################################
704    ###################################################################################
705
706    def add_other(
707        self,
708        name: str,
709        source_mapping: Tuple[str, int, int],
710        compilation_unit: "SlitherCompilationUnit",
711        additional_fields: Optional[Dict] = None,
712    ) -> None:
713        # If this a tuple with (filename, start, end), convert it to a source mapping.
714        if additional_fields is None:
715            additional_fields = {}
716        if isinstance(source_mapping, tuple):
717            # Parse the source id
718            (filename, start, end) = source_mapping
719            source_id = next(
720                (
721                    source_unit_id
722                    for (
723                        source_unit_id,
724                        source_unit_filename,
725                    ) in compilation_unit.source_units.items()
726                    if source_unit_filename == filename
727                ),
728                -1,
729            )
730
731            # Convert to a source mapping string
732            source_mapping = f"{start}:{end}:{source_id}"
733
734        # If this is a source mapping string, parse it.
735        if isinstance(source_mapping, str):
736            source_mapping_str = source_mapping
737            source_mapping = SourceMapping()
738            source_mapping.set_offset(source_mapping_str, compilation_unit)
739
740        # If this is a source mapping object, get the underlying source mapping dictionary
741        if isinstance(source_mapping, SourceMapping):
742            source_mapping = source_mapping.source_mapping.to_json()
743
744        # Create the underlying element and add it to our resulting json
745        element = _create_base_element("other", name, source_mapping, {}, additional_fields)
746        self._data["elements"].append(element)
logger = <Logger Slither (WARNING)>
def output_to_json(filename: Union[str, NoneType], error, results: Dict) -> None:
44def output_to_json(filename: Optional[str], error, results: Dict) -> None:
45    """
46
47    :param filename: Filename where the json will be written. If None or "-", write to stdout
48    :param error: Error to report
49    :param results: Results to report
50    :param logger: Logger where to log potential info
51    :return:
52    """
53    # Create our encapsulated JSON result.
54    json_result = {"success": error is None, "error": error, "results": results}
55
56    if filename == "-":
57        filename = None
58
59    # Determine if we should output to stdout
60    if filename is None:
61        # Write json to console
62        print(json.dumps(json_result))
63    else:
64        # Write json to file
65        if os.path.isfile(filename):
66            logger.info(yellow(f"{filename} exists already, the overwrite is prevented"))
67        else:
68            with open(filename, "w", encoding="utf8") as f:
69                json.dump(json_result, f, indent=2)
Parameters
  • filename: Filename where the json will be written. If None or "-", write to stdout
  • error: Error to report
  • results: Results to report
  • logger: Logger where to log potential info
Returns
def output_to_sarif( filename: Union[str, NoneType], results: Dict, detectors_classes: list[type[slither.detectors.abstract_detector.AbstractDetector]]) -> None:
143def output_to_sarif(
144    filename: Optional[str], results: Dict, detectors_classes: List[Type["AbstractDetector"]]
145) -> None:
146    """
147
148    :param filename:
149    :type filename:
150    :param results:
151    :type results:
152    :return:
153    :rtype:
154    """
155
156    sarif: Dict[str, Any] = {
157        "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
158        "version": "2.1.0",
159        "runs": [
160            {
161                "tool": {
162                    "driver": {
163                        "name": "Slither",
164                        "informationUri": "https://github.com/crytic/slither",
165                        "version": metadata.version("slither-analyzer"),
166                        "rules": [],
167                    }
168                },
169                "results": [],
170            }
171        ],
172    }
173
174    for detector in results.get("detectors", []):
175        _output_result_to_sarif(detector, detectors_classes, sarif)
176
177    if filename == "-":
178        filename = None
179
180    # Determine if we should output to stdout
181    if filename is None:
182        # Write json to console
183        print(json.dumps(sarif))
184    else:
185        # Write json to file
186        if os.path.isfile(filename):
187            logger.info(yellow(f"{filename} exists already, the overwrite is prevented"))
188        else:
189            with open(filename, "w", encoding="utf8") as f:
190                json.dump(sarif, f, indent=2)
Parameters
  • filename:
  • results:
Returns

ZIP_TYPES_ACCEPTED = {'lzma': 14, 'stored': 0, 'deflated': 8, 'bzip2': 12}
def output_to_zip( filename: str, error: Union[str, NoneType], results: Dict, zip_type: str = 'lzma'):
202def output_to_zip(filename: str, error: Optional[str], results: Dict, zip_type: str = "lzma"):
203    """
204    Output the results to a zip
205    The file in the zip is named slither_results.json
206    Note: the json file will not have indentation, as a result the resulting json file will be smaller
207    :param zip_type:
208    :param filename:
209    :param error:
210    :param results:
211    :return:
212    """
213    json_result = {"success": error is None, "error": error, "results": results}
214    if os.path.isfile(filename):
215        logger.info(yellow(f"{filename} exists already, the overwrite is prevented"))
216    else:
217        with ZipFile(
218            filename,
219            "w",
220            compression=ZIP_TYPES_ACCEPTED.get(zip_type, zipfile.ZIP_LZMA),
221        ) as file_desc:
222            file_desc.writestr("slither_results.json", json.dumps(json_result).encode("utf8"))

Output the results to a zip The file in the zip is named slither_results.json Note: the json file will not have indentation, as a result the resulting json file will be smaller

Parameters
  • zip_type:
  • filename:
  • error:
  • results:
Returns
class Output:
389class Output:
390    def __init__(
391        self,
392        info_: Union[str, List[Union[str, SupportedOutput]]],
393        additional_fields: Optional[Dict] = None,
394        markdown_root: str = "",
395        standard_format: bool = True,
396    ) -> None:
397        if additional_fields is None:
398            additional_fields = {}
399
400        # Allow info to be a string to simplify the API
401        info: List[Union[str, SupportedOutput]]
402        if isinstance(info_, str):
403            info = [info_]
404        else:
405            info = info_
406
407        self._data = OrderedDict()
408        self._data["elements"] = []
409        self._data["description"] = "".join(_convert_to_description(d) for d in info)
410        self._data["markdown"] = "".join(_convert_to_markdown(d, markdown_root) for d in info)
411        self._data["first_markdown_element"] = ""
412        self._markdown_root = markdown_root
413
414        id_txt = "".join(_convert_to_id(d) for d in info)
415        self._data["id"] = hashlib.sha3_256(id_txt.encode("utf-8")).hexdigest()
416
417        if standard_format:
418            to_add = [i for i in info if not isinstance(i, str)]
419
420            for add in to_add:
421                self.add(add)
422
423        if additional_fields:
424            self._data["additional_fields"] = additional_fields
425
426    def add(self, add: SupportedOutput, additional_fields: Optional[Dict] = None) -> None:
427        if not self._data["first_markdown_element"]:
428            self._data["first_markdown_element"] = add.source_mapping.to_markdown(
429                self._markdown_root
430            )
431        if isinstance(add, Variable):
432            self.add_variable(add, additional_fields=additional_fields)
433        elif isinstance(add, Contract):
434            self.add_contract(add, additional_fields=additional_fields)
435        elif isinstance(add, Function):
436            self.add_function(add, additional_fields=additional_fields)
437        elif isinstance(add, Enum):
438            self.add_enum(add, additional_fields=additional_fields)
439        elif isinstance(add, Event):
440            self.add_event(add, additional_fields=additional_fields)
441        elif isinstance(add, Structure):
442            self.add_struct(add, additional_fields=additional_fields)
443        elif isinstance(add, CustomError):
444            self.add_custom_error(add, additional_fields=additional_fields)
445        elif isinstance(add, Pragma):
446            self.add_pragma(add, additional_fields=additional_fields)
447        elif isinstance(add, Node):
448            self.add_node(add, additional_fields=additional_fields)
449        else:
450            raise SlitherError(f"Impossible to add {type(add)} to the json")
451
452    @property
453    def data(self) -> Dict:
454        return self._data
455
456    @property
457    def elements(self) -> List[Dict]:
458        return self._data["elements"]
459
460    # endregion
461    ###################################################################################
462    ###################################################################################
463    # region Variables
464    ###################################################################################
465    ###################################################################################
466
467    def add_variable(self, variable: Variable, additional_fields: Optional[Dict] = None) -> None:
468        if additional_fields is None:
469            additional_fields = {}
470        type_specific_fields = {"parent": _create_parent_element(variable)}
471        element = _create_base_element(
472            "variable",
473            variable.name,
474            variable.source_mapping.to_json(),
475            type_specific_fields,
476            additional_fields,
477        )
478        self._data["elements"].append(element)
479
480    def add_variables(self, variables: List[Variable]):
481        for variable in sorted(variables, key=lambda x: x.name):
482            self.add_variable(variable)
483
484    # endregion
485    ###################################################################################
486    ###################################################################################
487    # region Contract
488    ###################################################################################
489    ###################################################################################
490
491    def add_contract(self, contract: Contract, additional_fields: Optional[Dict] = None) -> None:
492        if additional_fields is None:
493            additional_fields = {}
494        element = _create_base_element(
495            "contract", contract.name, contract.source_mapping.to_json(), {}, additional_fields
496        )
497        self._data["elements"].append(element)
498
499    # endregion
500    ###################################################################################
501    ###################################################################################
502    # region Functions
503    ###################################################################################
504    ###################################################################################
505
506    def add_function(self, function: Function, additional_fields: Optional[Dict] = None) -> None:
507        if additional_fields is None:
508            additional_fields = {}
509        type_specific_fields = {
510            "parent": _create_parent_element(function),
511            "signature": function.full_name,
512        }
513        element = _create_base_element(
514            "function",
515            function.name,
516            function.source_mapping.to_json(),
517            type_specific_fields,
518            additional_fields,
519        )
520        self._data["elements"].append(element)
521
522    def add_functions(self, functions: List[Function], additional_fields: Optional[Dict] = None):
523        if additional_fields is None:
524            additional_fields = {}
525        for function in sorted(functions, key=lambda x: x.name):
526            self.add_function(function, additional_fields)
527
528    # endregion
529    ###################################################################################
530    ###################################################################################
531    # region Enum
532    ###################################################################################
533    ###################################################################################
534
535    def add_enum(self, enum: Enum, additional_fields: Optional[Dict] = None) -> None:
536        if additional_fields is None:
537            additional_fields = {}
538        type_specific_fields = {"parent": _create_parent_element(enum)}
539        element = _create_base_element(
540            "enum",
541            enum.name,
542            enum.source_mapping.to_json(),
543            type_specific_fields,
544            additional_fields,
545        )
546        self._data["elements"].append(element)
547
548    # endregion
549    ###################################################################################
550    ###################################################################################
551    # region Structures
552    ###################################################################################
553    ###################################################################################
554
555    def add_struct(self, struct: Structure, additional_fields: Optional[Dict] = None) -> None:
556        if additional_fields is None:
557            additional_fields = {}
558        type_specific_fields = {"parent": _create_parent_element(struct)}
559        element = _create_base_element(
560            "struct",
561            struct.name,
562            struct.source_mapping.to_json(),
563            type_specific_fields,
564            additional_fields,
565        )
566        self._data["elements"].append(element)
567
568    # endregion
569    ###################################################################################
570    ###################################################################################
571    # region Events
572    ###################################################################################
573    ###################################################################################
574
575    def add_event(self, event: Event, additional_fields: Optional[Dict] = None) -> None:
576        if additional_fields is None:
577            additional_fields = {}
578        type_specific_fields = {
579            "parent": _create_parent_element(event),
580            "signature": event.full_name,
581        }
582        element = _create_base_element(
583            "event",
584            event.name,
585            event.source_mapping.to_json(),
586            type_specific_fields,
587            additional_fields,
588        )
589
590        self._data["elements"].append(element)
591
592    # endregion
593    ###################################################################################
594    ###################################################################################
595    # region CustomError
596    ###################################################################################
597    ###################################################################################
598
599    def add_custom_error(
600        self, custom_error: CustomError, additional_fields: Optional[Dict] = None
601    ) -> None:
602        if additional_fields is None:
603            additional_fields = {}
604        type_specific_fields = {
605            "parent": _create_parent_element(custom_error),
606            "signature": custom_error.full_name,
607        }
608        element = _create_base_element(
609            "custom_error",
610            custom_error.name,
611            custom_error.source_mapping.to_json(),
612            type_specific_fields,
613            additional_fields,
614        )
615
616        self._data["elements"].append(element)
617
618    # endregion
619    ###################################################################################
620    ###################################################################################
621    # region Nodes
622    ###################################################################################
623    ###################################################################################
624
625    def add_node(self, node: Node, additional_fields: Optional[Dict] = None) -> None:
626        if additional_fields is None:
627            additional_fields = {}
628        type_specific_fields = {
629            "parent": _create_parent_element(node),
630        }
631        node_name = str(node.expression) if node.expression else ""
632        element = _create_base_element(
633            "node",
634            node_name,
635            node.source_mapping.to_json(),
636            type_specific_fields,
637            additional_fields,
638        )
639        self._data["elements"].append(element)
640
641    def add_nodes(self, nodes: List[Node]):
642        for node in sorted(nodes, key=lambda x: x.node_id):
643            self.add_node(node)
644
645    # endregion
646    ###################################################################################
647    ###################################################################################
648    # region Pragma
649    ###################################################################################
650    ###################################################################################
651
652    def add_pragma(self, pragma: Pragma, additional_fields: Optional[Dict] = None) -> None:
653        if additional_fields is None:
654            additional_fields = {}
655        type_specific_fields = {"directive": pragma.directive}
656        element = _create_base_element(
657            "pragma",
658            pragma.version,
659            pragma.source_mapping.to_json(),
660            type_specific_fields,
661            additional_fields,
662        )
663        self._data["elements"].append(element)
664
665    # endregion
666    ###################################################################################
667    ###################################################################################
668    # region File
669    ###################################################################################
670    ###################################################################################
671
672    def add_file(self, filename: str, content: str, additional_fields: Optional[Dict] = None):
673        if additional_fields is None:
674            additional_fields = {}
675        type_specific_fields = {"filename": filename, "content": content}
676        element = _create_base_element("file", type_specific_fields, additional_fields)
677
678        self._data["elements"].append(element)
679
680    # endregion
681    ###################################################################################
682    ###################################################################################
683    # region Pretty Table
684    ###################################################################################
685    ###################################################################################
686
687    def add_pretty_table(
688        self,
689        content: MyPrettyTable,
690        name: str,
691        additional_fields: Optional[Dict] = None,
692    ):
693        if additional_fields is None:
694            additional_fields = {}
695        type_specific_fields = {"content": content.to_json(), "name": name}
696        element = _create_base_element("pretty_table", type_specific_fields, additional_fields)
697
698        self._data["elements"].append(element)
699
700    # endregion
701    ###################################################################################
702    ###################################################################################
703    # region Others
704    ###################################################################################
705    ###################################################################################
706
707    def add_other(
708        self,
709        name: str,
710        source_mapping: Tuple[str, int, int],
711        compilation_unit: "SlitherCompilationUnit",
712        additional_fields: Optional[Dict] = None,
713    ) -> None:
714        # If this a tuple with (filename, start, end), convert it to a source mapping.
715        if additional_fields is None:
716            additional_fields = {}
717        if isinstance(source_mapping, tuple):
718            # Parse the source id
719            (filename, start, end) = source_mapping
720            source_id = next(
721                (
722                    source_unit_id
723                    for (
724                        source_unit_id,
725                        source_unit_filename,
726                    ) in compilation_unit.source_units.items()
727                    if source_unit_filename == filename
728                ),
729                -1,
730            )
731
732            # Convert to a source mapping string
733            source_mapping = f"{start}:{end}:{source_id}"
734
735        # If this is a source mapping string, parse it.
736        if isinstance(source_mapping, str):
737            source_mapping_str = source_mapping
738            source_mapping = SourceMapping()
739            source_mapping.set_offset(source_mapping_str, compilation_unit)
740
741        # If this is a source mapping object, get the underlying source mapping dictionary
742        if isinstance(source_mapping, SourceMapping):
743            source_mapping = source_mapping.source_mapping.to_json()
744
745        # Create the underlying element and add it to our resulting json
746        element = _create_base_element("other", name, source_mapping, {}, additional_fields)
747        self._data["elements"].append(element)
390    def __init__(
391        self,
392        info_: Union[str, List[Union[str, SupportedOutput]]],
393        additional_fields: Optional[Dict] = None,
394        markdown_root: str = "",
395        standard_format: bool = True,
396    ) -> None:
397        if additional_fields is None:
398            additional_fields = {}
399
400        # Allow info to be a string to simplify the API
401        info: List[Union[str, SupportedOutput]]
402        if isinstance(info_, str):
403            info = [info_]
404        else:
405            info = info_
406
407        self._data = OrderedDict()
408        self._data["elements"] = []
409        self._data["description"] = "".join(_convert_to_description(d) for d in info)
410        self._data["markdown"] = "".join(_convert_to_markdown(d, markdown_root) for d in info)
411        self._data["first_markdown_element"] = ""
412        self._markdown_root = markdown_root
413
414        id_txt = "".join(_convert_to_id(d) for d in info)
415        self._data["id"] = hashlib.sha3_256(id_txt.encode("utf-8")).hexdigest()
416
417        if standard_format:
418            to_add = [i for i in info if not isinstance(i, str)]
419
420            for add in to_add:
421                self.add(add)
422
423        if additional_fields:
424            self._data["additional_fields"] = additional_fields
426    def add(self, add: SupportedOutput, additional_fields: Optional[Dict] = None) -> None:
427        if not self._data["first_markdown_element"]:
428            self._data["first_markdown_element"] = add.source_mapping.to_markdown(
429                self._markdown_root
430            )
431        if isinstance(add, Variable):
432            self.add_variable(add, additional_fields=additional_fields)
433        elif isinstance(add, Contract):
434            self.add_contract(add, additional_fields=additional_fields)
435        elif isinstance(add, Function):
436            self.add_function(add, additional_fields=additional_fields)
437        elif isinstance(add, Enum):
438            self.add_enum(add, additional_fields=additional_fields)
439        elif isinstance(add, Event):
440            self.add_event(add, additional_fields=additional_fields)
441        elif isinstance(add, Structure):
442            self.add_struct(add, additional_fields=additional_fields)
443        elif isinstance(add, CustomError):
444            self.add_custom_error(add, additional_fields=additional_fields)
445        elif isinstance(add, Pragma):
446            self.add_pragma(add, additional_fields=additional_fields)
447        elif isinstance(add, Node):
448            self.add_node(add, additional_fields=additional_fields)
449        else:
450            raise SlitherError(f"Impossible to add {type(add)} to the json")
data: Dict
452    @property
453    def data(self) -> Dict:
454        return self._data
elements: List[Dict]
456    @property
457    def elements(self) -> List[Dict]:
458        return self._data["elements"]
def add_variable( self, variable: slither.core.variables.variable.Variable, additional_fields: Union[Dict, NoneType] = None) -> None:
467    def add_variable(self, variable: Variable, additional_fields: Optional[Dict] = None) -> None:
468        if additional_fields is None:
469            additional_fields = {}
470        type_specific_fields = {"parent": _create_parent_element(variable)}
471        element = _create_base_element(
472            "variable",
473            variable.name,
474            variable.source_mapping.to_json(),
475            type_specific_fields,
476            additional_fields,
477        )
478        self._data["elements"].append(element)
def add_variables(self, variables: List[slither.core.variables.variable.Variable]):
480    def add_variables(self, variables: List[Variable]):
481        for variable in sorted(variables, key=lambda x: x.name):
482            self.add_variable(variable)
def add_contract( self, contract: slither.core.declarations.contract.Contract, additional_fields: Union[Dict, NoneType] = None) -> None:
491    def add_contract(self, contract: Contract, additional_fields: Optional[Dict] = None) -> None:
492        if additional_fields is None:
493            additional_fields = {}
494        element = _create_base_element(
495            "contract", contract.name, contract.source_mapping.to_json(), {}, additional_fields
496        )
497        self._data["elements"].append(element)
def add_function( self, function: slither.core.declarations.function.Function, additional_fields: Union[Dict, NoneType] = None) -> None:
506    def add_function(self, function: Function, additional_fields: Optional[Dict] = None) -> None:
507        if additional_fields is None:
508            additional_fields = {}
509        type_specific_fields = {
510            "parent": _create_parent_element(function),
511            "signature": function.full_name,
512        }
513        element = _create_base_element(
514            "function",
515            function.name,
516            function.source_mapping.to_json(),
517            type_specific_fields,
518            additional_fields,
519        )
520        self._data["elements"].append(element)
def add_functions( self, functions: List[slither.core.declarations.function.Function], additional_fields: Union[Dict, NoneType] = None):
522    def add_functions(self, functions: List[Function], additional_fields: Optional[Dict] = None):
523        if additional_fields is None:
524            additional_fields = {}
525        for function in sorted(functions, key=lambda x: x.name):
526            self.add_function(function, additional_fields)
def add_enum( self, enum: slither.core.declarations.enum.Enum, additional_fields: Union[Dict, NoneType] = None) -> None:
535    def add_enum(self, enum: Enum, additional_fields: Optional[Dict] = None) -> None:
536        if additional_fields is None:
537            additional_fields = {}
538        type_specific_fields = {"parent": _create_parent_element(enum)}
539        element = _create_base_element(
540            "enum",
541            enum.name,
542            enum.source_mapping.to_json(),
543            type_specific_fields,
544            additional_fields,
545        )
546        self._data["elements"].append(element)
def add_struct( self, struct: slither.core.declarations.structure.Structure, additional_fields: Union[Dict, NoneType] = None) -> None:
555    def add_struct(self, struct: Structure, additional_fields: Optional[Dict] = None) -> None:
556        if additional_fields is None:
557            additional_fields = {}
558        type_specific_fields = {"parent": _create_parent_element(struct)}
559        element = _create_base_element(
560            "struct",
561            struct.name,
562            struct.source_mapping.to_json(),
563            type_specific_fields,
564            additional_fields,
565        )
566        self._data["elements"].append(element)
def add_event( self, event: slither.core.declarations.event.Event, additional_fields: Union[Dict, NoneType] = None) -> None:
575    def add_event(self, event: Event, additional_fields: Optional[Dict] = None) -> None:
576        if additional_fields is None:
577            additional_fields = {}
578        type_specific_fields = {
579            "parent": _create_parent_element(event),
580            "signature": event.full_name,
581        }
582        element = _create_base_element(
583            "event",
584            event.name,
585            event.source_mapping.to_json(),
586            type_specific_fields,
587            additional_fields,
588        )
589
590        self._data["elements"].append(element)
def add_custom_error( self, custom_error: slither.core.declarations.custom_error.CustomError, additional_fields: Union[Dict, NoneType] = None) -> None:
599    def add_custom_error(
600        self, custom_error: CustomError, additional_fields: Optional[Dict] = None
601    ) -> None:
602        if additional_fields is None:
603            additional_fields = {}
604        type_specific_fields = {
605            "parent": _create_parent_element(custom_error),
606            "signature": custom_error.full_name,
607        }
608        element = _create_base_element(
609            "custom_error",
610            custom_error.name,
611            custom_error.source_mapping.to_json(),
612            type_specific_fields,
613            additional_fields,
614        )
615
616        self._data["elements"].append(element)
def add_node( self, node: slither.core.cfg.node.Node, additional_fields: Union[Dict, NoneType] = None) -> None:
625    def add_node(self, node: Node, additional_fields: Optional[Dict] = None) -> None:
626        if additional_fields is None:
627            additional_fields = {}
628        type_specific_fields = {
629            "parent": _create_parent_element(node),
630        }
631        node_name = str(node.expression) if node.expression else ""
632        element = _create_base_element(
633            "node",
634            node_name,
635            node.source_mapping.to_json(),
636            type_specific_fields,
637            additional_fields,
638        )
639        self._data["elements"].append(element)
def add_nodes(self, nodes: List[slither.core.cfg.node.Node]):
641    def add_nodes(self, nodes: List[Node]):
642        for node in sorted(nodes, key=lambda x: x.node_id):
643            self.add_node(node)
def add_pragma( self, pragma: slither.core.declarations.pragma_directive.Pragma, additional_fields: Union[Dict, NoneType] = None) -> None:
652    def add_pragma(self, pragma: Pragma, additional_fields: Optional[Dict] = None) -> None:
653        if additional_fields is None:
654            additional_fields = {}
655        type_specific_fields = {"directive": pragma.directive}
656        element = _create_base_element(
657            "pragma",
658            pragma.version,
659            pragma.source_mapping.to_json(),
660            type_specific_fields,
661            additional_fields,
662        )
663        self._data["elements"].append(element)
def add_file( self, filename: str, content: str, additional_fields: Union[Dict, NoneType] = None):
672    def add_file(self, filename: str, content: str, additional_fields: Optional[Dict] = None):
673        if additional_fields is None:
674            additional_fields = {}
675        type_specific_fields = {"filename": filename, "content": content}
676        element = _create_base_element("file", type_specific_fields, additional_fields)
677
678        self._data["elements"].append(element)
def add_pretty_table( self, content: slither.utils.myprettytable.MyPrettyTable, name: str, additional_fields: Union[Dict, NoneType] = None):
687    def add_pretty_table(
688        self,
689        content: MyPrettyTable,
690        name: str,
691        additional_fields: Optional[Dict] = None,
692    ):
693        if additional_fields is None:
694            additional_fields = {}
695        type_specific_fields = {"content": content.to_json(), "name": name}
696        element = _create_base_element("pretty_table", type_specific_fields, additional_fields)
697
698        self._data["elements"].append(element)
def add_other( self, name: str, source_mapping: Tuple[str, int, int], compilation_unit: slither.core.compilation_unit.SlitherCompilationUnit, additional_fields: Union[Dict, NoneType] = None) -> None:
707    def add_other(
708        self,
709        name: str,
710        source_mapping: Tuple[str, int, int],
711        compilation_unit: "SlitherCompilationUnit",
712        additional_fields: Optional[Dict] = None,
713    ) -> None:
714        # If this a tuple with (filename, start, end), convert it to a source mapping.
715        if additional_fields is None:
716            additional_fields = {}
717        if isinstance(source_mapping, tuple):
718            # Parse the source id
719            (filename, start, end) = source_mapping
720            source_id = next(
721                (
722                    source_unit_id
723                    for (
724                        source_unit_id,
725                        source_unit_filename,
726                    ) in compilation_unit.source_units.items()
727                    if source_unit_filename == filename
728                ),
729                -1,
730            )
731
732            # Convert to a source mapping string
733            source_mapping = f"{start}:{end}:{source_id}"
734
735        # If this is a source mapping string, parse it.
736        if isinstance(source_mapping, str):
737            source_mapping_str = source_mapping
738            source_mapping = SourceMapping()
739            source_mapping.set_offset(source_mapping_str, compilation_unit)
740
741        # If this is a source mapping object, get the underlying source mapping dictionary
742        if isinstance(source_mapping, SourceMapping):
743            source_mapping = source_mapping.source_mapping.to_json()
744
745        # Create the underlying element and add it to our resulting json
746        element = _create_base_element("other", name, source_mapping, {}, additional_fields)
747        self._data["elements"].append(element)