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
SupportedOutput =
typing.Union[slither.core.variables.variable.Variable, slither.core.declarations.contract.Contract, slither.core.declarations.function.Function, slither.core.declarations.enum.Enum, slither.core.declarations.event.Event, slither.core.declarations.structure.Structure, slither.core.declarations.pragma_directive.Pragma, slither.core.cfg.node.Node]
AllSupportedOutput =
typing.Union[str, slither.core.variables.variable.Variable, slither.core.declarations.contract.Contract, slither.core.declarations.function.Function, slither.core.declarations.enum.Enum, slither.core.declarations.event.Event, slither.core.declarations.structure.Structure, slither.core.declarations.pragma_directive.Pragma, slither.core.cfg.node.Node]
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)
Output( info_: Union[str, List[Union[str, slither.core.variables.variable.Variable, slither.core.declarations.contract.Contract, slither.core.declarations.function.Function, slither.core.declarations.enum.Enum, slither.core.declarations.event.Event, slither.core.declarations.structure.Structure, slither.core.declarations.pragma_directive.Pragma, slither.core.cfg.node.Node]]], additional_fields: Union[Dict, NoneType] = None, markdown_root: str = '', standard_format: bool = True)
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
def
add( self, add: Union[slither.core.variables.variable.Variable, slither.core.declarations.contract.Contract, slither.core.declarations.function.Function, slither.core.declarations.enum.Enum, slither.core.declarations.event.Event, slither.core.declarations.structure.Structure, slither.core.declarations.pragma_directive.Pragma, slither.core.cfg.node.Node], additional_fields: Union[Dict, NoneType] = None) -> None:
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")
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_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):
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_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)