slither.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import logging
  2. import os
  3. from crytic_compile import CryticCompile, InvalidCompilation
  4. # pylint: disable= no-name-in-module
  5. from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
  6. from slither.printers.abstract_printer import AbstractPrinter
  7. from slither.core.slither_core import SlitherCore
  8. from slither.exceptions import SlitherError
  9. from slither.solc_parsing.slitherSolc import SlitherSolc
  10. logger = logging.getLogger("Slither")
  11. logging.basicConfig()
  12. logger_detector = logging.getLogger("Detectors")
  13. logger_printer = logging.getLogger("Printers")
  14. def _check_common_things(thing_name, cls, base_cls, instances_list):
  15. if not issubclass(cls, base_cls) or cls is base_cls:
  16. raise Exception(
  17. "You can't register {!r} as a {}. You need to pass a class that inherits from {}".format(
  18. cls, thing_name, base_cls.__name__
  19. )
  20. )
  21. if any(type(obj) == cls for obj in instances_list): # pylint: disable=unidiomatic-typecheck
  22. raise Exception("You can't register {!r} twice.".format(cls))
  23. class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
  24. def __init__(self, target, **kwargs):
  25. """
  26. Args:
  27. target (str | list(json) | CryticCompile)
  28. Keyword Args:
  29. solc (str): solc binary location (default 'solc')
  30. disable_solc_warnings (bool): True to disable solc warnings (default false)
  31. solc_arguments (str): solc arguments (default '')
  32. ast_format (str): ast format (default '--ast-compact-json')
  33. filter_paths (list(str)): list of path to filter (default [])
  34. triage_mode (bool): if true, switch to triage mode (default false)
  35. exclude_dependencies (bool): if true, exclude results that are only related to dependencies
  36. generate_patches (bool): if true, patches are generated (json output only)
  37. truffle_ignore (bool): ignore truffle.js presence (default false)
  38. truffle_build_directory (str): build truffle directory (default 'build/contracts')
  39. truffle_ignore_compile (bool): do not run truffle compile (default False)
  40. truffle_version (str): use a specific truffle version (default None)
  41. embark_ignore (bool): ignore embark.js presence (default false)
  42. embark_ignore_compile (bool): do not run embark build (default False)
  43. embark_overwrite_config (bool): overwrite original config file (default false)
  44. """
  45. super().__init__()
  46. self._parser: SlitherSolc # This could be another parser, like SlitherVyper, interface needs to be determined
  47. self._disallow_partial: bool = kwargs.get("disallow_partial", False)
  48. self._skip_assembly: bool = kwargs.get("skip_assembly", False)
  49. # list of files provided (see --splitted option)
  50. if isinstance(target, list):
  51. self._init_from_list(target)
  52. elif isinstance(target, str) and target.endswith(".json"):
  53. self._init_from_raw_json(target)
  54. else:
  55. self._parser = SlitherSolc("", self)
  56. try:
  57. if isinstance(target, CryticCompile):
  58. crytic_compile = target
  59. else:
  60. crytic_compile = CryticCompile(target, **kwargs)
  61. self._crytic_compile = crytic_compile
  62. except InvalidCompilation as e:
  63. # pylint: disable=raise-missing-from
  64. raise SlitherError(f"Invalid compilation: \n{str(e)}")
  65. for path, ast in crytic_compile.asts.items():
  66. self._parser.parse_contracts_from_loaded_json(ast, path)
  67. self.add_source_code(path)
  68. if kwargs.get("generate_patches", False):
  69. self.generate_patches = True
  70. self._markdown_root = kwargs.get("markdown_root", "")
  71. self._detectors = []
  72. self._printers = []
  73. filter_paths = kwargs.get("filter_paths", [])
  74. for p in filter_paths:
  75. self.add_path_to_filter(p)
  76. self._exclude_dependencies = kwargs.get("exclude_dependencies", False)
  77. triage_mode = kwargs.get("triage_mode", False)
  78. self._triage_mode = triage_mode
  79. self._parser.parse_contracts()
  80. # skip_analyze is only used for testing
  81. if not kwargs.get("skip_analyze", False):
  82. self._parser.analyze_contracts()
  83. def _init_from_raw_json(self, filename):
  84. if not os.path.isfile(filename):
  85. raise SlitherError(
  86. "{} does not exist (are you in the correct directory?)".format(filename)
  87. )
  88. assert filename.endswith("json")
  89. with open(filename, encoding="utf8") as astFile:
  90. stdout = astFile.read()
  91. if not stdout:
  92. to_log = f"Empty AST file: {filename}"
  93. raise SlitherError(to_log)
  94. contracts_json = stdout.split("\n=")
  95. self._parser = SlitherSolc(filename, self)
  96. for c in contracts_json:
  97. self._parser.parse_contracts_from_json(c)
  98. def _init_from_list(self, contract):
  99. self._parser = SlitherSolc("", self)
  100. for c in contract:
  101. if "absolutePath" in c:
  102. path = c["absolutePath"]
  103. else:
  104. path = c["attributes"]["absolutePath"]
  105. self._parser.parse_contracts_from_loaded_json(c, path)
  106. @property
  107. def detectors(self):
  108. return self._detectors
  109. @property
  110. def detectors_high(self):
  111. return [d for d in self.detectors if d.IMPACT == DetectorClassification.HIGH]
  112. @property
  113. def detectors_medium(self):
  114. return [d for d in self.detectors if d.IMPACT == DetectorClassification.MEDIUM]
  115. @property
  116. def detectors_low(self):
  117. return [d for d in self.detectors if d.IMPACT == DetectorClassification.LOW]
  118. @property
  119. def detectors_informational(self):
  120. return [d for d in self.detectors if d.IMPACT == DetectorClassification.INFORMATIONAL]
  121. @property
  122. def detectors_optimization(self):
  123. return [d for d in self.detectors if d.IMPACT == DetectorClassification.OPTIMIZATION]
  124. def register_detector(self, detector_class):
  125. """
  126. :param detector_class: Class inheriting from `AbstractDetector`.
  127. """
  128. _check_common_things("detector", detector_class, AbstractDetector, self._detectors)
  129. instance = detector_class(self, logger_detector)
  130. self._detectors.append(instance)
  131. def register_printer(self, printer_class):
  132. """
  133. :param printer_class: Class inheriting from `AbstractPrinter`.
  134. """
  135. _check_common_things("printer", printer_class, AbstractPrinter, self._printers)
  136. instance = printer_class(self, logger_printer)
  137. self._printers.append(instance)
  138. def run_detectors(self):
  139. """
  140. :return: List of registered detectors results.
  141. """
  142. self.load_previous_results()
  143. results = [d.detect() for d in self._detectors]
  144. self.write_results_to_hide()
  145. return results
  146. def run_printers(self):
  147. """
  148. :return: List of registered printers outputs.
  149. """
  150. return [p.output(self.filename).data for p in self._printers]
  151. @property
  152. def triage_mode(self):
  153. return self._triage_mode