__main__.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  1. #!/usr/bin/env python3
  2. import argparse
  3. import cProfile
  4. import glob
  5. import inspect
  6. import json
  7. import logging
  8. import os
  9. import pstats
  10. import sys
  11. import traceback
  12. from typing import Optional
  13. from pkg_resources import iter_entry_points, require
  14. from crytic_compile import cryticparser
  15. from crytic_compile.platform.standard import generate_standard_export
  16. from crytic_compile import compile_all, is_supported
  17. from slither.detectors import all_detectors
  18. from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
  19. from slither.printers import all_printers
  20. from slither.printers.abstract_printer import AbstractPrinter
  21. from slither.slither import Slither
  22. from slither.utils.output import output_to_json, output_to_zip, ZIP_TYPES_ACCEPTED
  23. from slither.utils.output_capture import StandardOutputCapture
  24. from slither.utils.colors import red, blue, set_colorization_enabled
  25. from slither.utils.command_line import (
  26. output_detectors,
  27. output_results_to_markdown,
  28. output_detectors_json,
  29. output_printers,
  30. output_printers_json,
  31. output_to_markdown,
  32. output_wiki,
  33. defaults_flag_in_config,
  34. read_config_file,
  35. JSON_OUTPUT_TYPES,
  36. DEFAULT_JSON_OUTPUT_TYPES,
  37. )
  38. from slither.exceptions import SlitherException
  39. logging.basicConfig()
  40. logger = logging.getLogger("Slither")
  41. ###################################################################################
  42. ###################################################################################
  43. # region Process functions
  44. ###################################################################################
  45. ###################################################################################
  46. def process_single(target, args, detector_classes, printer_classes):
  47. """
  48. The core high-level code for running Slither static analysis.
  49. Returns:
  50. list(result), int: Result list and number of contracts analyzed
  51. """
  52. ast = "--ast-compact-json"
  53. if args.legacy_ast:
  54. ast = "--ast-json"
  55. slither = Slither(target, ast_format=ast, **vars(args))
  56. return _process(slither, detector_classes, printer_classes)
  57. def process_all(target, args, detector_classes, printer_classes):
  58. compilations = compile_all(target, **vars(args))
  59. slither_instances = []
  60. results_detectors = []
  61. results_printers = []
  62. analyzed_contracts_count = 0
  63. for compilation in compilations:
  64. (
  65. slither,
  66. current_results_detectors,
  67. current_results_printers,
  68. current_analyzed_count,
  69. ) = process_single(compilation, args, detector_classes, printer_classes)
  70. results_detectors.extend(current_results_detectors)
  71. results_printers.extend(current_results_printers)
  72. slither_instances.append(slither)
  73. analyzed_contracts_count += current_analyzed_count
  74. return (
  75. slither_instances,
  76. results_detectors,
  77. results_printers,
  78. analyzed_contracts_count,
  79. )
  80. def _process(slither, detector_classes, printer_classes):
  81. for detector_cls in detector_classes:
  82. slither.register_detector(detector_cls)
  83. for printer_cls in printer_classes:
  84. slither.register_printer(printer_cls)
  85. analyzed_contracts_count = len(slither.contracts)
  86. results_detectors = []
  87. results_printers = []
  88. if not printer_classes:
  89. detector_results = slither.run_detectors()
  90. detector_results = [x for x in detector_results if x] # remove empty results
  91. detector_results = [item for sublist in detector_results for item in sublist] # flatten
  92. results_detectors.extend(detector_results)
  93. else:
  94. printer_results = slither.run_printers()
  95. printer_results = [x for x in printer_results if x] # remove empty results
  96. results_printers.extend(printer_results)
  97. return slither, results_detectors, results_printers, analyzed_contracts_count
  98. def process_from_asts(filenames, args, detector_classes, printer_classes):
  99. all_contracts = []
  100. for filename in filenames:
  101. with open(filename, encoding="utf8") as file_open:
  102. contract_loaded = json.load(file_open)
  103. all_contracts.append(contract_loaded["ast"])
  104. return process_single(all_contracts, args, detector_classes, printer_classes)
  105. # endregion
  106. ###################################################################################
  107. ###################################################################################
  108. # region Exit
  109. ###################################################################################
  110. ###################################################################################
  111. def my_exit(results):
  112. if not results:
  113. sys.exit(0)
  114. sys.exit(len(results))
  115. # endregion
  116. ###################################################################################
  117. ###################################################################################
  118. # region Detectors and printers
  119. ###################################################################################
  120. ###################################################################################
  121. def get_detectors_and_printers():
  122. """
  123. NOTE: This contains just a few detectors and printers that we made public.
  124. """
  125. detectors = [getattr(all_detectors, name) for name in dir(all_detectors)]
  126. detectors = [d for d in detectors if inspect.isclass(d) and issubclass(d, AbstractDetector)]
  127. printers = [getattr(all_printers, name) for name in dir(all_printers)]
  128. printers = [p for p in printers if inspect.isclass(p) and issubclass(p, AbstractPrinter)]
  129. # Handle plugins!
  130. for entry_point in iter_entry_points(group="slither_analyzer.plugin", name=None):
  131. make_plugin = entry_point.load()
  132. plugin_detectors, plugin_printers = make_plugin()
  133. detector = None
  134. if not all(issubclass(detector, AbstractDetector) for detector in plugin_detectors):
  135. raise Exception(
  136. "Error when loading plugin %s, %r is not a detector" % (entry_point, detector)
  137. )
  138. printer = None
  139. if not all(issubclass(printer, AbstractPrinter) for printer in plugin_printers):
  140. raise Exception(
  141. "Error when loading plugin %s, %r is not a printer" % (entry_point, printer)
  142. )
  143. # We convert those to lists in case someone returns a tuple
  144. detectors += list(plugin_detectors)
  145. printers += list(plugin_printers)
  146. return detectors, printers
  147. # pylint: disable=too-many-branches
  148. def choose_detectors(args, all_detector_classes):
  149. # If detectors are specified, run only these ones
  150. detectors_to_run = []
  151. detectors = {d.ARGUMENT: d for d in all_detector_classes}
  152. if args.detectors_to_run == "all":
  153. detectors_to_run = all_detector_classes
  154. if args.detectors_to_exclude:
  155. detectors_excluded = args.detectors_to_exclude.split(",")
  156. for detector in detectors:
  157. if detector in detectors_excluded:
  158. detectors_to_run.remove(detectors[detector])
  159. else:
  160. for detector in args.detectors_to_run.split(","):
  161. if detector in detectors:
  162. detectors_to_run.append(detectors[detector])
  163. else:
  164. raise Exception("Error: {} is not a detector".format(detector))
  165. detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT)
  166. return detectors_to_run
  167. if args.exclude_optimization:
  168. detectors_to_run = [
  169. d for d in detectors_to_run if d.IMPACT != DetectorClassification.OPTIMIZATION
  170. ]
  171. if args.exclude_informational:
  172. detectors_to_run = [
  173. d for d in detectors_to_run if d.IMPACT != DetectorClassification.INFORMATIONAL
  174. ]
  175. if args.exclude_low:
  176. detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.LOW]
  177. if args.exclude_medium:
  178. detectors_to_run = [
  179. d for d in detectors_to_run if d.IMPACT != DetectorClassification.MEDIUM
  180. ]
  181. if args.exclude_high:
  182. detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.HIGH]
  183. if args.detectors_to_exclude:
  184. detectors_to_run = [
  185. d for d in detectors_to_run if d.ARGUMENT not in args.detectors_to_exclude
  186. ]
  187. detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT)
  188. return detectors_to_run
  189. def choose_printers(args, all_printer_classes):
  190. printers_to_run = []
  191. # disable default printer
  192. if args.printers_to_run is None:
  193. return []
  194. if args.printers_to_run == "all":
  195. return all_printer_classes
  196. printers = {p.ARGUMENT: p for p in all_printer_classes}
  197. for printer in args.printers_to_run.split(","):
  198. if printer in printers:
  199. printers_to_run.append(printers[printer])
  200. else:
  201. raise Exception("Error: {} is not a printer".format(printer))
  202. return printers_to_run
  203. # endregion
  204. ###################################################################################
  205. ###################################################################################
  206. # region Command line parsing
  207. ###################################################################################
  208. ###################################################################################
  209. def parse_filter_paths(args):
  210. if args.filter_paths:
  211. return args.filter_paths.split(",")
  212. return []
  213. def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-statements
  214. parser = argparse.ArgumentParser(
  215. description="Slither. For usage information, see https://github.com/crytic/slither/wiki/Usage",
  216. usage="slither.py contract.sol [flag]",
  217. )
  218. parser.add_argument("filename", help="contract.sol")
  219. cryticparser.init(parser)
  220. parser.add_argument(
  221. "--version",
  222. help="displays the current version",
  223. version=require("slither-analyzer")[0].version,
  224. action="version",
  225. )
  226. group_detector = parser.add_argument_group("Detectors")
  227. group_printer = parser.add_argument_group("Printers")
  228. group_misc = parser.add_argument_group("Additional options")
  229. group_detector.add_argument(
  230. "--detect",
  231. help="Comma-separated list of detectors, defaults to all, "
  232. "available detectors: {}".format(", ".join(d.ARGUMENT for d in detector_classes)),
  233. action="store",
  234. dest="detectors_to_run",
  235. default=defaults_flag_in_config["detectors_to_run"],
  236. )
  237. group_printer.add_argument(
  238. "--print",
  239. help="Comma-separated list fo contract information printers, "
  240. "available printers: {}".format(", ".join(d.ARGUMENT for d in printer_classes)),
  241. action="store",
  242. dest="printers_to_run",
  243. default=defaults_flag_in_config["printers_to_run"],
  244. )
  245. group_detector.add_argument(
  246. "--list-detectors",
  247. help="List available detectors",
  248. action=ListDetectors,
  249. nargs=0,
  250. default=False,
  251. )
  252. group_printer.add_argument(
  253. "--list-printers",
  254. help="List available printers",
  255. action=ListPrinters,
  256. nargs=0,
  257. default=False,
  258. )
  259. group_detector.add_argument(
  260. "--exclude",
  261. help="Comma-separated list of detectors that should be excluded",
  262. action="store",
  263. dest="detectors_to_exclude",
  264. default=defaults_flag_in_config["detectors_to_exclude"],
  265. )
  266. group_detector.add_argument(
  267. "--exclude-dependencies",
  268. help="Exclude results that are only related to dependencies",
  269. action="store_true",
  270. default=defaults_flag_in_config["exclude_dependencies"],
  271. )
  272. group_detector.add_argument(
  273. "--exclude-optimization",
  274. help="Exclude optimization analyses",
  275. action="store_true",
  276. default=defaults_flag_in_config["exclude_optimization"],
  277. )
  278. group_detector.add_argument(
  279. "--exclude-informational",
  280. help="Exclude informational impact analyses",
  281. action="store_true",
  282. default=defaults_flag_in_config["exclude_informational"],
  283. )
  284. group_detector.add_argument(
  285. "--exclude-low",
  286. help="Exclude low impact analyses",
  287. action="store_true",
  288. default=defaults_flag_in_config["exclude_low"],
  289. )
  290. group_detector.add_argument(
  291. "--exclude-medium",
  292. help="Exclude medium impact analyses",
  293. action="store_true",
  294. default=defaults_flag_in_config["exclude_medium"],
  295. )
  296. group_detector.add_argument(
  297. "--exclude-high",
  298. help="Exclude high impact analyses",
  299. action="store_true",
  300. default=defaults_flag_in_config["exclude_high"],
  301. )
  302. group_detector.add_argument(
  303. "--show-ignored-findings",
  304. help="Show all the findings",
  305. action="store_true",
  306. default=defaults_flag_in_config["show_ignored_findings"],
  307. )
  308. group_misc.add_argument(
  309. "--json",
  310. help='Export the results as a JSON file ("--json -" to export to stdout)',
  311. action="store",
  312. default=defaults_flag_in_config["json"],
  313. )
  314. group_misc.add_argument(
  315. "--json-types",
  316. help="Comma-separated list of result types to output to JSON, defaults to "
  317. + f'{",".join(output_type for output_type in DEFAULT_JSON_OUTPUT_TYPES)}. '
  318. + f'Available types: {",".join(output_type for output_type in JSON_OUTPUT_TYPES)}',
  319. action="store",
  320. default=defaults_flag_in_config["json-types"],
  321. )
  322. group_misc.add_argument(
  323. "--zip",
  324. help="Export the results as a zipped JSON file",
  325. action="store",
  326. default=defaults_flag_in_config["zip"],
  327. )
  328. group_misc.add_argument(
  329. "--zip-type",
  330. help=f'Zip compression type. One of {",".join(ZIP_TYPES_ACCEPTED.keys())}. Default lzma',
  331. action="store",
  332. default=defaults_flag_in_config["zip_type"],
  333. )
  334. group_misc.add_argument(
  335. "--markdown-root", help="URL for markdown generation", action="store", default="",
  336. )
  337. group_misc.add_argument(
  338. "--disable-color",
  339. help="Disable output colorization",
  340. action="store_true",
  341. default=defaults_flag_in_config["disable_color"],
  342. )
  343. group_misc.add_argument(
  344. "--filter-paths",
  345. help="Comma-separated list of paths for which results will be excluded",
  346. action="store",
  347. dest="filter_paths",
  348. default=defaults_flag_in_config["filter_paths"],
  349. )
  350. group_misc.add_argument(
  351. "--triage-mode",
  352. help="Run triage mode (save results in slither.db.json)",
  353. action="store_true",
  354. dest="triage_mode",
  355. default=False,
  356. )
  357. group_misc.add_argument(
  358. "--config-file",
  359. help="Provide a config file (default: slither.config.json)",
  360. action="store",
  361. dest="config_file",
  362. default="slither.config.json",
  363. )
  364. group_misc.add_argument(
  365. "--solc-ast", help="Provide the contract as a json AST", action="store_true", default=False,
  366. )
  367. group_misc.add_argument(
  368. "--generate-patches",
  369. help="Generate patches (json output only)",
  370. action="store_true",
  371. default=False,
  372. )
  373. # debugger command
  374. parser.add_argument("--debug", help=argparse.SUPPRESS, action="store_true", default=False)
  375. parser.add_argument("--markdown", help=argparse.SUPPRESS, action=OutputMarkdown, default=False)
  376. group_misc.add_argument(
  377. "--checklist", help=argparse.SUPPRESS, action="store_true", default=False
  378. )
  379. parser.add_argument(
  380. "--wiki-detectors", help=argparse.SUPPRESS, action=OutputWiki, default=False
  381. )
  382. parser.add_argument(
  383. "--list-detectors-json",
  384. help=argparse.SUPPRESS,
  385. action=ListDetectorsJson,
  386. nargs=0,
  387. default=False,
  388. )
  389. parser.add_argument(
  390. "--legacy-ast",
  391. help=argparse.SUPPRESS,
  392. action="store_true",
  393. default=defaults_flag_in_config["legacy_ast"],
  394. )
  395. parser.add_argument(
  396. "--skip-assembly",
  397. help=argparse.SUPPRESS,
  398. action="store_true",
  399. default=defaults_flag_in_config["skip_assembly"],
  400. )
  401. parser.add_argument(
  402. "--ignore-return-value",
  403. help=argparse.SUPPRESS,
  404. action="store_true",
  405. default=defaults_flag_in_config["ignore_return_value"],
  406. )
  407. parser.add_argument(
  408. "--perf", help=argparse.SUPPRESS, action="store_true", default=False,
  409. )
  410. # if the json is splitted in different files
  411. parser.add_argument("--splitted", help=argparse.SUPPRESS, action="store_true", default=False)
  412. # Disable the throw/catch on partial analyses
  413. parser.add_argument(
  414. "--disallow-partial", help=argparse.SUPPRESS, action="store_true", default=False
  415. )
  416. if len(sys.argv) == 1:
  417. parser.print_help(sys.stderr)
  418. sys.exit(1)
  419. args = parser.parse_args()
  420. read_config_file(args)
  421. args.filter_paths = parse_filter_paths(args)
  422. # Verify our json-type output is valid
  423. args.json_types = set(args.json_types.split(","))
  424. for json_type in args.json_types:
  425. if json_type not in JSON_OUTPUT_TYPES:
  426. raise Exception(f'Error: "{json_type}" is not a valid JSON result output type.')
  427. return args
  428. class ListDetectors(argparse.Action): # pylint: disable=too-few-public-methods
  429. def __call__(self, parser, *args, **kwargs): # pylint: disable=signature-differs
  430. detectors, _ = get_detectors_and_printers()
  431. output_detectors(detectors)
  432. parser.exit()
  433. class ListDetectorsJson(argparse.Action): # pylint: disable=too-few-public-methods
  434. def __call__(self, parser, *args, **kwargs): # pylint: disable=signature-differs
  435. detectors, _ = get_detectors_and_printers()
  436. detector_types_json = output_detectors_json(detectors)
  437. print(json.dumps(detector_types_json))
  438. parser.exit()
  439. class ListPrinters(argparse.Action): # pylint: disable=too-few-public-methods
  440. def __call__(self, parser, *args, **kwargs): # pylint: disable=signature-differs
  441. _, printers = get_detectors_and_printers()
  442. output_printers(printers)
  443. parser.exit()
  444. class OutputMarkdown(argparse.Action): # pylint: disable=too-few-public-methods
  445. def __call__(self, parser, args, values, option_string=None):
  446. detectors, printers = get_detectors_and_printers()
  447. output_to_markdown(detectors, printers, values)
  448. parser.exit()
  449. class OutputWiki(argparse.Action): # pylint: disable=too-few-public-methods
  450. def __call__(self, parser, args, values, option_string=None):
  451. detectors, _ = get_detectors_and_printers()
  452. output_wiki(detectors, values)
  453. parser.exit()
  454. # endregion
  455. ###################################################################################
  456. ###################################################################################
  457. # region CustomFormatter
  458. ###################################################################################
  459. ###################################################################################
  460. class FormatterCryticCompile(logging.Formatter):
  461. def format(self, record):
  462. # for i, msg in enumerate(record.msg):
  463. if record.msg.startswith("Compilation warnings/errors on "):
  464. txt = record.args[1]
  465. txt = txt.split("\n")
  466. txt = [red(x) if "Error" in x else x for x in txt]
  467. txt = "\n".join(txt)
  468. record.args = (record.args[0], txt)
  469. return super().format(record)
  470. # endregion
  471. ###################################################################################
  472. ###################################################################################
  473. # region Main
  474. ###################################################################################
  475. ###################################################################################
  476. def main():
  477. # Codebase with complex domninators can lead to a lot of SSA recursive call
  478. sys.setrecursionlimit(1500)
  479. detectors, printers = get_detectors_and_printers()
  480. main_impl(all_detector_classes=detectors, all_printer_classes=printers)
  481. # pylint: disable=too-many-statements,too-many-branches,too-many-locals
  482. def main_impl(all_detector_classes, all_printer_classes):
  483. """
  484. :param all_detector_classes: A list of all detectors that can be included/excluded.
  485. :param all_printer_classes: A list of all printers that can be included.
  486. """
  487. # Set logger of Slither to info, to catch warnings related to the arg parsing
  488. logger.setLevel(logging.INFO)
  489. args = parse_args(all_detector_classes, all_printer_classes)
  490. cp: Optional[cProfile.Profile] = None
  491. if args.perf:
  492. cp = cProfile.Profile()
  493. cp.enable()
  494. # Set colorization option
  495. set_colorization_enabled(not args.disable_color)
  496. # Define some variables for potential JSON output
  497. json_results = {}
  498. output_error = None
  499. outputting_json = args.json is not None
  500. outputting_json_stdout = args.json == "-"
  501. outputting_zip = args.zip is not None
  502. if args.zip_type not in ZIP_TYPES_ACCEPTED.keys():
  503. to_log = f'Zip type not accepted, it must be one of {",".join(ZIP_TYPES_ACCEPTED.keys())}'
  504. logger.error(to_log)
  505. # If we are outputting JSON, capture all standard output. If we are outputting to stdout, we block typical stdout
  506. # output.
  507. if outputting_json:
  508. StandardOutputCapture.enable(outputting_json_stdout)
  509. printer_classes = choose_printers(args, all_printer_classes)
  510. detector_classes = choose_detectors(args, all_detector_classes)
  511. default_log = logging.INFO if not args.debug else logging.DEBUG
  512. for (l_name, l_level) in [
  513. ("Slither", default_log),
  514. ("Contract", default_log),
  515. ("Function", default_log),
  516. ("Node", default_log),
  517. ("Parsing", default_log),
  518. ("Detectors", default_log),
  519. ("FunctionSolc", default_log),
  520. ("ExpressionParsing", default_log),
  521. ("TypeParsing", default_log),
  522. ("SSA_Conversion", default_log),
  523. ("Printers", default_log),
  524. # ('CryticCompile', default_log)
  525. ]:
  526. logger_level = logging.getLogger(l_name)
  527. logger_level.setLevel(l_level)
  528. console_handler = logging.StreamHandler()
  529. console_handler.setLevel(logging.INFO)
  530. console_handler.setFormatter(FormatterCryticCompile())
  531. crytic_compile_error = logging.getLogger(("CryticCompile"))
  532. crytic_compile_error.addHandler(console_handler)
  533. crytic_compile_error.propagate = False
  534. crytic_compile_error.setLevel(logging.INFO)
  535. results_detectors = []
  536. results_printers = []
  537. try:
  538. filename = args.filename
  539. # Determine if we are handling ast from solc
  540. if args.solc_ast or (filename.endswith(".json") and not is_supported(filename)):
  541. globbed_filenames = glob.glob(filename, recursive=True)
  542. filenames = glob.glob(os.path.join(filename, "*.json"))
  543. if not filenames:
  544. filenames = globbed_filenames
  545. number_contracts = 0
  546. slither_instances = []
  547. if args.splitted:
  548. (
  549. slither_instance,
  550. results_detectors,
  551. results_printers,
  552. number_contracts,
  553. ) = process_from_asts(filenames, args, detector_classes, printer_classes)
  554. slither_instances.append(slither_instance)
  555. else:
  556. for filename in filenames:
  557. (
  558. slither_instance,
  559. results_detectors_tmp,
  560. results_printers_tmp,
  561. number_contracts_tmp,
  562. ) = process_single(filename, args, detector_classes, printer_classes)
  563. number_contracts += number_contracts_tmp
  564. results_detectors += results_detectors_tmp
  565. results_printers += results_printers_tmp
  566. slither_instances.append(slither_instance)
  567. # Rely on CryticCompile to discern the underlying type of compilations.
  568. else:
  569. (
  570. slither_instances,
  571. results_detectors,
  572. results_printers,
  573. number_contracts,
  574. ) = process_all(filename, args, detector_classes, printer_classes)
  575. # Determine if we are outputting JSON
  576. if outputting_json or outputting_zip:
  577. # Add our compilation information to JSON
  578. if "compilations" in args.json_types:
  579. compilation_results = []
  580. for slither_instance in slither_instances:
  581. compilation_results.append(
  582. generate_standard_export(slither_instance.crytic_compile)
  583. )
  584. json_results["compilations"] = compilation_results
  585. # Add our detector results to JSON if desired.
  586. if results_detectors and "detectors" in args.json_types:
  587. json_results["detectors"] = results_detectors
  588. # Add our printer results to JSON if desired.
  589. if results_printers and "printers" in args.json_types:
  590. json_results["printers"] = results_printers
  591. # Add our detector types to JSON
  592. if "list-detectors" in args.json_types:
  593. detectors, _ = get_detectors_and_printers()
  594. json_results["list-detectors"] = output_detectors_json(detectors)
  595. # Add our detector types to JSON
  596. if "list-printers" in args.json_types:
  597. _, printers = get_detectors_and_printers()
  598. json_results["list-printers"] = output_printers_json(printers)
  599. # Output our results to markdown if we wish to compile a checklist.
  600. if args.checklist:
  601. output_results_to_markdown(results_detectors)
  602. # Dont print the number of result for printers
  603. if number_contracts == 0:
  604. logger.warning(red("No contract was analyzed"))
  605. if printer_classes:
  606. logger.info("%s analyzed (%d contracts)", filename, number_contracts)
  607. else:
  608. logger.info(
  609. "%s analyzed (%d contracts with %d detectors), %d result(s) found",
  610. filename,
  611. number_contracts,
  612. len(detector_classes),
  613. len(results_detectors),
  614. )
  615. logger.info(
  616. blue(
  617. "Use https://crytic.io/ to get access to additional detectors and Github integration"
  618. )
  619. )
  620. if args.ignore_return_value:
  621. return
  622. except SlitherException as slither_exception:
  623. output_error = str(slither_exception)
  624. traceback.print_exc()
  625. logging.error(red("Error:"))
  626. logging.error(red(output_error))
  627. logging.error("Please report an issue to https://github.com/crytic/slither/issues")
  628. except Exception: # pylint: disable=broad-except
  629. output_error = traceback.format_exc()
  630. logging.error(traceback.print_exc())
  631. logging.error(f"Error in {args.filename}") # pylint: disable=logging-fstring-interpolation
  632. logging.error(output_error)
  633. # If we are outputting JSON, capture the redirected output and disable the redirect to output the final JSON.
  634. if outputting_json:
  635. if "console" in args.json_types:
  636. json_results["console"] = {
  637. "stdout": StandardOutputCapture.get_stdout_output(),
  638. "stderr": StandardOutputCapture.get_stderr_output(),
  639. }
  640. StandardOutputCapture.disable()
  641. output_to_json(None if outputting_json_stdout else args.json, output_error, json_results)
  642. if outputting_zip:
  643. output_to_zip(args.zip, output_error, json_results, args.zip_type)
  644. if args.perf:
  645. cp.disable()
  646. stats = pstats.Stats(cp).sort_stats("cumtime")
  647. stats.print_stats()
  648. # Exit with the appropriate status code
  649. if output_error:
  650. sys.exit(-1)
  651. else:
  652. my_exit(results_detectors)
  653. if __name__ == "__main__":
  654. main()
  655. # endregion