diff --git a/scripts/ci_test.sh b/scripts/ci_test.sh index 2ef5b03576..6696a4e890 100755 --- a/scripts/ci_test.sh +++ b/scripts/ci_test.sh @@ -13,8 +13,7 @@ test_slither(){ expected="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.json" # run slither detector on input file and save output as json - slither "$1" --solc-disable-warnings --detect "$2" --json "$DIR/tmp-test.json" - if [ $? -eq 255 ] + if ! slither "$1" --solc-disable-warnings --detect "$2" --json "$DIR/tmp-test.json"; then echo "Slither crashed" exit 255 @@ -40,8 +39,7 @@ test_slither(){ fi # run slither detector on input file and save output as json - slither "$1" --solc-disable-warnings --detect "$2" --legacy-ast --json "$DIR/tmp-test.json" - if [ $? -eq 255 ] + if ! slither "$1" --solc-disable-warnings --detect "$2" --legacy-ast --json "$DIR/tmp-test.json"; then echo "Slither crashed" exit 255 diff --git a/scripts/ci_test_cli.sh b/scripts/ci_test_cli.sh index 9bfe63d5b1..56e6ff0a40 100755 --- a/scripts/ci_test_cli.sh +++ b/scripts/ci_test_cli.sh @@ -4,17 +4,17 @@ solc-select use 0.7.0 -if ! slither "tests/config/test.sol" --solc-ast --ignore-return-value; then +if ! slither "tests/config/test.sol" --solc-ast; then echo "--solc-ast failed" exit 1 fi -if ! slither "tests/config/test.sol" --solc-disable-warnings --ignore-return-value; then +if ! slither "tests/config/test.sol" --solc-disable-warnings; then echo "--solc-disable-warnings failed" exit 1 fi -if ! slither "tests/config/test.sol" --disable-color --ignore-return-value; then +if ! slither "tests/config/test.sol" --disable-color; then echo "--disable-color failed" exit 1 fi diff --git a/scripts/ci_test_dapp.sh b/scripts/ci_test_dapp.sh index 354e437325..b5051b1312 100755 --- a/scripts/ci_test_dapp.sh +++ b/scripts/ci_test_dapp.sh @@ -15,9 +15,9 @@ nix-env -f "$HOME/.dapp/dapptools" -iA dapp seth solc hevm ethsign dapp init -if slither . --detect external-function; then - exit 0 +if ! slither . --detect external-function; then + echo "Dapp test failed" + exit 1 fi -echo "Dapp test failed" -exit 255 +exit 0 diff --git a/scripts/ci_test_embark.sh b/scripts/ci_test_embark.sh index ad34ffa4fd..4bcb6f78c3 100755 --- a/scripts/ci_test_embark.sh +++ b/scripts/ci_test_embark.sh @@ -15,13 +15,10 @@ npm install -g embark@4.2.0 embark demo cd embark_demo || exit 255 npm install -slither . --embark-overwrite-config -if [ $? -eq 4 ] -then - exit 0 +if ! slither . --embark-overwrite-config; then + echo "Embark test failed" + exit 255 fi -echo "Embark test failed" -exit 255 - +exit 0 diff --git a/scripts/ci_test_etherlime.sh b/scripts/ci_test_etherlime.sh index 9a2f240807..53d3d33501 100755 --- a/scripts/ci_test_etherlime.sh +++ b/scripts/ci_test_etherlime.sh @@ -13,12 +13,10 @@ nvm use 10.17.0 npm i -g etherlime etherlime init -slither . -if [ $? -eq 7 ] -then - exit 0 +if ! slither .; then + echo "Etherlime test failed" + exit 1 fi -echo "Etherlime test failed" -exit 255 +exit 0 \ No newline at end of file diff --git a/scripts/ci_test_etherscan.sh b/scripts/ci_test_etherscan.sh index 08a160f509..c8e69958cb 100755 --- a/scripts/ci_test_etherscan.sh +++ b/scripts/ci_test_etherscan.sh @@ -5,19 +5,15 @@ mkdir etherscan cd etherscan || exit 255 -slither 0x7F37f78cBD74481E593F9C737776F7113d76B315 --etherscan-apikey "$GITHUB_ETHERSCAN" - -if [ $? -ne 5 ] -then +if ! slither 0x7F37f78cBD74481E593F9C737776F7113d76B315 --etherscan-apikey "$GITHUB_ETHERSCAN"; then echo "Etherscan test failed" - exit 255 + exit 1 fi -slither rinkeby:0xFe05820C5A92D9bc906D4A46F662dbeba794d3b7 --etherscan-apikey "$GITHUB_ETHERSCAN" - -if [ $? -ne 70 ] -then +if ! slither rinkeby:0xFe05820C5A92D9bc906D4A46F662dbeba794d3b7 --etherscan-apikey "$GITHUB_ETHERSCAN"; then echo "Etherscan test failed" - exit 255 + exit 1 fi +exit 0 + diff --git a/scripts/ci_test_find_paths.sh b/scripts/ci_test_find_paths.sh index a916fb5a98..1c3652745e 100755 --- a/scripts/ci_test_find_paths.sh +++ b/scripts/ci_test_find_paths.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -### Test slither-check-upgradability +### Test slither-check-upgradeability DIR_TESTS="tests/possible_paths" diff --git a/scripts/ci_test_truffle.sh b/scripts/ci_test_truffle.sh index b8473b26d3..da1a350c90 100755 --- a/scripts/ci_test_truffle.sh +++ b/scripts/ci_test_truffle.sh @@ -13,12 +13,10 @@ nvm use --lts npm install -g truffle truffle unbox metacoin -slither . -if [ $? -eq 3 ] -then - exit 0 +if ! slither .; then + echo "Truffle test failed" + exit 1 fi -echo "Truffle test failed" -exit 255 +exit 0 \ No newline at end of file diff --git a/slither/__main__.py b/slither/__main__.py index d56ac5072b..b27c8ae750 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -10,7 +10,7 @@ import pstats import sys import traceback -from typing import Optional +from typing import Tuple, Optional, List, Dict from pkg_resources import iter_entry_points, require @@ -54,7 +54,12 @@ ################################################################################### -def process_single(target, args, detector_classes, printer_classes): +def process_single( + target: str, + args: argparse.Namespace, + detector_classes: List[AbstractDetector], + printer_classes: List[AbstractPrinter], +) -> Tuple[Slither, List[Dict], List[Dict], int]: """ The core high-level code for running Slither static analysis. @@ -72,7 +77,12 @@ def process_single(target, args, detector_classes, printer_classes): return _process(slither, detector_classes, printer_classes) -def process_all(target, args, detector_classes, printer_classes): +def process_all( + target: str, + args: argparse.Namespace, + detector_classes: List[AbstractDetector], + printer_classes: List[AbstractPrinter], +) -> Tuple[List[Slither], List[Dict], List[Dict], int]: compilations = compile_all(target, **vars(args)) slither_instances = [] results_detectors = [] @@ -97,7 +107,11 @@ def process_all(target, args, detector_classes, printer_classes): ) -def _process(slither, detector_classes, printer_classes): +def _process( + slither: Slither, + detector_classes: List[AbstractDetector], + printer_classes: List[AbstractPrinter], +) -> Tuple[Slither, List[Dict], List[Dict], int]: for detector_cls in detector_classes: slither.register_detector(detector_cls) @@ -123,7 +137,12 @@ def _process(slither, detector_classes, printer_classes): return slither, results_detectors, results_printers, analyzed_contracts_count -def process_from_asts(filenames, args, detector_classes, printer_classes): +def process_from_asts( + filenames: List[str], + args: argparse.Namespace, + detector_classes: List[AbstractDetector], + printer_classes: List[AbstractPrinter], +): all_contracts = [] for filename in filenames: @@ -137,29 +156,13 @@ def process_from_asts(filenames, args, detector_classes, printer_classes): # endregion ################################################################################### ################################################################################### -# region Exit -################################################################################### -################################################################################### - - -def my_exit(results): - if not results: - sys.exit(0) - sys.exit(len(results)) - -# endregion -################################################################################### -################################################################################### # region Detectors and printers ################################################################################### ################################################################################### def get_detectors_and_printers(): - """ - NOTE: This contains just a few detectors and printers that we made public. - """ detectors = [getattr(all_detectors, name) for name in dir(all_detectors)] detectors = [d for d in detectors if inspect.isclass(d) and issubclass(d, AbstractDetector)] @@ -190,7 +193,9 @@ def get_detectors_and_printers(): # pylint: disable=too-many-branches -def choose_detectors(args, all_detector_classes): +def choose_detectors( + args: argparse.Namespace, all_detector_classes: List[AbstractDetector] +) -> List[AbstractDetector]: # If detectors are specified, run only these ones detectors_to_run = [] @@ -212,22 +217,22 @@ def choose_detectors(args, all_detector_classes): detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT) return detectors_to_run - if args.exclude_optimization: + if args.exclude_optimization and not args.fail_pedantic: detectors_to_run = [ d for d in detectors_to_run if d.IMPACT != DetectorClassification.OPTIMIZATION ] - if args.exclude_informational: + if args.exclude_informational and not args.fail_pedantic: detectors_to_run = [ d for d in detectors_to_run if d.IMPACT != DetectorClassification.INFORMATIONAL ] - if args.exclude_low: + if args.exclude_low and not args.fail_low: detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.LOW] - if args.exclude_medium: + if args.exclude_medium and not args.fail_medium: detectors_to_run = [ d for d in detectors_to_run if d.IMPACT != DetectorClassification.MEDIUM ] - if args.exclude_high: + if args.exclude_high and not args.fail_high: detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.HIGH] if args.detectors_to_exclude: detectors_to_run = [ @@ -239,7 +244,9 @@ def choose_detectors(args, all_detector_classes): return detectors_to_run -def choose_printers(args, all_printer_classes): +def choose_printers( + args: argparse.Namespace, all_printer_classes: List[AbstractPrinter] +) -> List[AbstractPrinter]: printers_to_run = [] # disable default printer @@ -388,6 +395,34 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s default=defaults_flag_in_config["exclude_high"], ) + group_detector.add_argument( + "--fail-pedantic", + help="Fail if any finding is detected", + action="store_true", + default=defaults_flag_in_config["fail_pedantic"], + ) + + group_detector.add_argument( + "--fail-low", + help="Fail if low or greater impact finding is detected", + action="store_true", + default=defaults_flag_in_config["fail_low"], + ) + + group_detector.add_argument( + "--fail-medium", + help="Fail if medium or greater impact finding is detected", + action="store_true", + default=defaults_flag_in_config["fail_medium"], + ) + + group_detector.add_argument( + "--fail-high", + help="Fail if high impact finding is detected", + action="store_true", + default=defaults_flag_in_config["fail_high"], + ) + group_detector.add_argument( "--show-ignored-findings", help="Show all the findings", @@ -538,13 +573,6 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s default=defaults_flag_in_config["skip_assembly"], ) - parser.add_argument( - "--ignore-return-value", - help=argparse.SUPPRESS, - action="store_true", - default=defaults_flag_in_config["ignore_return_value"], - ) - parser.add_argument( "--perf", help=argparse.SUPPRESS, @@ -652,7 +680,9 @@ def main(): # pylint: disable=too-many-statements,too-many-branches,too-many-locals -def main_impl(all_detector_classes, all_printer_classes): +def main_impl( + all_detector_classes: List[AbstractDetector], all_printer_classes: List[AbstractPrinter] +): """ :param all_detector_classes: A list of all detectors that can be included/excluded. :param all_printer_classes: A list of all printers that can be included. @@ -808,8 +838,6 @@ def main_impl(all_detector_classes, all_printer_classes): len(detector_classes), len(results_detectors), ) - if args.ignore_return_value: - return except SlitherException as slither_exception: output_error = str(slither_exception) @@ -848,11 +876,26 @@ def main_impl(all_detector_classes, all_printer_classes): stats = pstats.Stats(cp).sort_stats("cumtime") stats.print_stats() - # Exit with the appropriate status code - if output_error: + if args.fail_high: + fail_on_detection = any(result["impact"] == "High" for result in results_detectors) + elif args.fail_medium: + fail_on_detection = any( + result["impact"] in ["Medium", "High"] for result in results_detectors + ) + elif args.fail_low: + fail_on_detection = any( + result["impact"] in ["Low", "Medium", "High"] for result in results_detectors + ) + elif args.fail_pedantic: + fail_on_detection = bool(results_detectors) + else: + fail_on_detection = False + + # Exit with them appropriate status code + if output_error or fail_on_detection: sys.exit(-1) else: - my_exit(results_detectors) + sys.exit(0) if __name__ == "__main__": diff --git a/slither/detectors/abstract_detector.py b/slither/detectors/abstract_detector.py index f2ba734db9..528981c924 100644 --- a/slither/detectors/abstract_detector.py +++ b/slither/detectors/abstract_detector.py @@ -147,7 +147,7 @@ def _detect(self) -> List[Output]: # pylint: disable=too-many-branches def detect(self) -> List[Dict]: results: List[Dict] = [] - # only keep valid result, and remove dupplicate + # only keep valid result, and remove duplicate # Keep only dictionaries for r in [output.data for output in self._detect()]: if self.compilation_unit.core.valid_result(r) and r not in results: diff --git a/slither/utils/command_line.py b/slither/utils/command_line.py index 6d5ad1e611..d0d77c45f2 100644 --- a/slither/utils/command_line.py +++ b/slither/utils/command_line.py @@ -34,6 +34,10 @@ "exclude_low": False, "exclude_medium": False, "exclude_high": False, + "fail_pedantic": False, + "fail_low": False, + "fail_medium": False, + "fail_high": False, "json": None, "sarif": None, "json-types": ",".join(DEFAULT_JSON_OUTPUT_TYPES), @@ -43,7 +47,6 @@ # debug command "skip_assembly": False, "legacy_ast": False, - "ignore_return_value": False, "zip": None, "zip_type": "lzma", "show_ignored_findings": False, diff --git a/slither/utils/output.py b/slither/utils/output.py index 41b0275493..6296e35d38 100644 --- a/slither/utils/output.py +++ b/slither/utils/output.py @@ -22,7 +22,6 @@ logger = logging.getLogger("Slither") - ################################################################################### ################################################################################### # region Output @@ -359,7 +358,7 @@ def __init__( else: info = info_ - self._data: Dict[str, Any] = OrderedDict() + self._data = OrderedDict() self._data["elements"] = [] self._data["description"] = "".join(_convert_to_description(d) for d in info) self._data["markdown"] = "".join(_convert_to_markdown(d, markdown_root) for d in info)