From ee1b4c251d2fe180af6c58ff2a4d3d4b5c781827 Mon Sep 17 00:00:00 2001 From: Josselin Date: Fri, 5 Apr 2019 12:04:13 +0100 Subject: [PATCH] Refactor Slither entry point: a Slither object will now detect by itself if the directory is a truffle or embark one. The advantage is to simplify: - external scripts and utils (ex: possible-paths/check-upgradability support embark natively) - __main__.pyn Changes: - Remove is_truffle/is_embark flag - Move truffle compile to Sliter._init_from_truffle - Refactor help, there are two new option groups: truffle and embark - Rename --ignore-truffle-compile to truffle-ignore-compile --- slither/__main__.py | 123 +++++++++---------------------- slither/slither.py | 76 ++++++++++++++----- utils/possible_paths/__main__.py | 2 +- utils/upgradability/__main__.py | 6 +- 4 files changed, 96 insertions(+), 111 deletions(-) diff --git a/slither/__main__.py b/slither/__main__.py index 0810f34ff..104323cca 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -6,7 +6,6 @@ import inspect import json import logging import os -import platform import subprocess import sys import traceback @@ -49,6 +48,10 @@ def process(filename, args, detector_classes, printer_classes): disable_solc_warnings=args.disable_solc_warnings, solc_arguments=args.solc_args, ast_format=ast, + truffle_build_directory=args.truffle_build_directory, + truffle_ignore_compile=args.truffle_ignore_compile, + truffle_version=args.truffle_version, + embark_overwrite_config=args.embark_overwrite_config, filter_paths=parse_filter_paths(args), triage_mode=args.triage_mode) @@ -76,62 +79,6 @@ def _process(slither, detector_classes, printer_classes): return results, analyzed_contracts_count -def process_truffle(dirname, args, detector_classes, printer_classes): - # Truffle on windows has naming conflicts where it will invoke truffle.js directly instead - # of truffle.cmd (unless in powershell or git bash). The cleanest solution is to explicitly call - # truffle.cmd. Reference: - # https://truffleframework.com/docs/truffle/reference/configuration#resolving-naming-conflicts-on-windows - if not args.ignore_truffle_compile: - truffle_base_command = "truffle" if platform.system() != 'Windows' else "truffle.cmd" - cmd = [truffle_base_command, 'compile'] - if args.truffle_version: - cmd = ['npx',args.truffle_version,'compile'] - elif os.path.isfile('package.json'): - with open('package.json') as f: - package = json.load(f) - if 'devDependencies' in package: - if 'truffle' in package['devDependencies']: - version = package['devDependencies']['truffle'] - if version.startswith('^'): - version = version[1:] - truffle_version = 'truffle@{}'.format(version) - cmd = ['npx', truffle_version,'compile'] - logger.info("'{}' running (use --truffle-version truffle@x.x.x to use specific version)".format(' '.join(cmd))) - process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - stdout, stderr = process.communicate() - stdout, stderr = stdout.decode(), stderr.decode() # convert bytestrings to unicode strings - - logger.info(stdout) - - if stderr: - logger.error(stderr) - - slither = Slither(dirname, - solc=args.solc, - disable_solc_warnings=args.disable_solc_warnings, - solc_arguments=args.solc_args, - is_truffle=True, - truffle_build_directory=args.truffle_build_directory, - filter_paths=parse_filter_paths(args), - triage_mode=args.triage_mode) - - return _process(slither, detector_classes, printer_classes) - -def process_embark(dirname, args, detector_classes, printer_classes): - - slither = Slither(dirname, - solc=args.solc, - disable_solc_warnings=args.disable_solc_warnings, - solc_arguments=args.solc_args, - is_truffle=False, - is_embark=True, - embark_overwrite_config=args.embark_overwrite_config, - filter_paths=parse_filter_paths(args), - triage_mode=args.triage_mode) - - return _process(slither, detector_classes, printer_classes) - def process_files(filenames, args, detector_classes, printer_classes): all_contracts = [] @@ -299,14 +246,14 @@ defaults_flag_in_config = { 'truffle_version': None, 'disable_color': False, 'filter_paths': None, - 'ignore_truffle_compile': False, + 'truffle_ignore_compile': False, 'truffle_build_directory': 'build/contracts', 'embark_overwrite_config': False, 'legacy_ast': False } def parse_args(detector_classes, printer_classes): - parser = argparse.ArgumentParser(description='Slither', + parser = argparse.ArgumentParser(description='Slither. For usage information, see https://github.com/crytic/slither/wiki/Usage', usage="slither.py contract.sol [flag]") parser.add_argument('filename', @@ -320,6 +267,8 @@ def parse_args(detector_classes, printer_classes): group_detector = parser.add_argument_group('Detectors') group_printer = parser.add_argument_group('Printers') group_solc = parser.add_argument_group('Solc options') + group_truffle = parser.add_argument_group('Truffle options') + group_embark = parser.add_argument_group('Embark options') group_misc = parser.add_argument_group('Additional option') group_detector.add_argument('--detect', @@ -396,14 +345,33 @@ def parse_args(detector_classes, printer_classes): action='store_true', default=False) + group_truffle.add_argument('--truffle-ignore-compile', + help='Do not run truffle compile', + action='store_true', + dest='truffle_ignore_compile', + default=defaults_flag_in_config['truffle_ignore_compile']) + + group_truffle.add_argument('--truffle-build-directory', + help='Do not run truffle compile', + action='store', + dest='truffle_build_directory', + default=defaults_flag_in_config['truffle_build_directory']) + + group_truffle.add_argument('--truffle-version', + help='Use a local Truffle version (with npx)', + action='store', + default=defaults_flag_in_config['truffle_version']) + + group_embark.add_argument('--embark-overwrite-config', + help='Install @trailofbits/embark-contract-export and add it to embark.json', + action='store_true', + default=defaults_flag_in_config['embark_overwrite_config']) + group_misc.add_argument('--json', help='Export results as JSON', action='store', default=defaults_flag_in_config['json']) - group_misc.add_argument('--truffle-version', - help='Use a local Truffle version (with npx)', - action='store', - default=defaults_flag_in_config['truffle_version']) + group_misc.add_argument('--disable-color', help='Disable output colorization', @@ -416,18 +384,6 @@ def parse_args(detector_classes, printer_classes): dest='filter_paths', default=defaults_flag_in_config['filter_paths']) - group_misc.add_argument('--ignore-truffle-compile', - help='Do not run truffle compile', - action='store_true', - dest='ignore_truffle_compile', - default=defaults_flag_in_config['ignore_truffle_compile']) - - group_misc.add_argument('--truffle-build-directory', - help='Do not run truffle compile', - action='store', - dest='truffle_build_directory', - default=defaults_flag_in_config['truffle_build_directory']) - group_misc.add_argument('--triage-mode', help='Run triage mode (save results in slither.db.json)', action='store_true', @@ -457,11 +413,6 @@ def parse_args(detector_classes, printer_classes): action='store_true', default=False) - group_misc.add_argument('--embark-overwrite-config', - help=argparse.SUPPRESS, - action='store_true', - default=defaults_flag_in_config['embark_overwrite_config']) - parser.add_argument('--wiki-detectors', help=argparse.SUPPRESS, action=OutputWiki, @@ -588,15 +539,12 @@ def main_impl(all_detector_classes, all_printer_classes): globbed_filenames = glob.glob(filename, recursive=True) - if os.path.isfile(filename): + if os.path.isfile(filename) or\ + os.path.isfile(os.path.join(filename, 'truffle.js')) or\ + os.path.isfile(os.path.join(filename, 'truffle-config.js')) or\ + os.path.isfile(os.path.join(filename, 'embark.json')): (results, number_contracts) = process(filename, args, detector_classes, printer_classes) - elif os.path.isfile(os.path.join(filename, 'truffle.js')) or os.path.isfile(os.path.join(filename, 'truffle-config.js')): - (results, number_contracts) = process_truffle(filename, args, detector_classes, printer_classes) - - elif os.path.isfile(os.path.join(filename, 'embark.json')): - (results, number_contracts) = process_embark(filename, args, detector_classes, printer_classes) - elif os.path.isdir(filename) or len(globbed_filenames) > 0: extension = "*.sol" if not args.solc_ast else "*.json" filenames = glob.glob(os.path.join(filename, extension)) @@ -612,7 +560,6 @@ def main_impl(all_detector_classes, all_printer_classes): number_contracts += number_contracts_tmp results += results_tmp - else: raise Exception("Unrecognised file/dir path: '#{filename}'".format(filename=filename)) diff --git a/slither/slither.py b/slither/slither.py index c24ec05a7..c0c181226 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -4,6 +4,7 @@ import subprocess import sys import glob import json +import platform from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.printers.abstract_printer import AbstractPrinter @@ -28,28 +29,36 @@ class Slither(SlitherSolc): disable_solc_warnings (bool): True to disable solc warnings (default false) solc_argeuments (str): solc arguments (default '') ast_format (str): ast format (default '--ast-compact-json') - is_truffle (bool): is a truffle directory (default false) - truffle_build_directory (str): build truffle directory (default 'build/contracts') - is_embark (bool): is an embark directory (default false) - embark_overwrite_config (bool): overwrite original config file (default false) filter_paths (list(str)): list of path to filter (default []) triage_mode (bool): if true, switch to triage mode (default false) - ''' - is_truffle = kwargs.get('is_truffle', False) + truffle_ignore (bool): ignore truffle.js presence (default false) + truffle_build_directory (str): build truffle directory (default 'build/contracts') + truffle_ignore_compile (bool): do not run truffle compile (default False) + truffle_version (str): use a specific truffle version (default None) - is_embark = kwargs.get('is_embark', False) - embark_overwrite_config = kwargs.get('embark_overwrite_config', False) + embark_ignore (bool): ignore embark.js presence (default false) + embark_overwrite_config (bool): overwrite original config file (default false) + + ''' + + truffle_ignore = kwargs.get('truffle_ignore', False) + embark_ignore = kwargs.get('embark_ignore', False) - # truffle directory - if is_truffle: - self._init_from_truffle(contract, kwargs.get('truffle_build_directory', 'build/contracts')) - # embark directory - elif is_embark: - self._init_from_embark(contract, embark_overwrite_config) # list of files provided (see --splitted option) - elif isinstance(contract, list): + if isinstance(contract, list): self._init_from_list(contract) + # truffle directory + elif not truffle_ignore and (os.path.isfile(os.path.join(contract, 'truffle.js')) or + os.path.isfile(os.path.join(contract, 'truffle-config.js'))): + self._init_from_truffle(contract, + kwargs.get('truffle_build_directory', 'build/contracts'), + kwargs.get('truffle_ignore_compile', False), + kwargs.get('truffle_version', None)) + # embark directory + elif not embark_ignore and os.path.isfile(os.path.join(contract, 'embark.json')): + self._init_from_embark(contract, + kwargs.get('embark_overwrite_config', False)) # .json or .sol provided else: self._init_from_solc(contract, **kwargs) @@ -85,7 +94,7 @@ class Slither(SlitherSolc): with open('embark.json', 'w') as outfile: json.dump(embark_json, outfile, indent=2) else: - if (not 'plugins' in embark_json) or (not 'embark-contract-info' in embark_json['plugins']): + if (not 'plugins' in embark_json) or (not plugin_name in embark_json['plugins']): logger.error(red('embark-contract-info plugin was found in embark.json. Please install the plugin (see https://github.com/crytic/slither/wiki/Usage#embark), or use --embark-overwrite-config.')) sys.exit(-1) @@ -96,7 +105,6 @@ class Slither(SlitherSolc): # Embark might return information to stderr, but compile without issue logger.error("%s"%stderr.decode()) infile = os.path.join(contract, 'crytic-export', 'contracts.json') - print(infile) if not os.path.isfile(infile): logger.error(red('Embark did not generate the AST file. Is Embark installed (npm install -g embark)? Is embark-contract-info installed? (npm install -g embark).')) sys.exit(-1) @@ -104,9 +112,39 @@ class Slither(SlitherSolc): contracts_loaded = json.load(f) contracts_loaded = contracts_loaded['asts'] for contract_loaded in contracts_loaded: - self._parse_contracts_from_loaded_json(contract_loaded, contract_loaded['absolutePath']) + self._parse_contracts_from_loaded_json(contract_loaded, + contract_loaded['absolutePath']) + + def _init_from_truffle(self, contract, build_directory, truffle_ignore_compile, truffle_version): + # Truffle on windows has naming conflicts where it will invoke truffle.js directly instead + # of truffle.cmd (unless in powershell or git bash). The cleanest solution is to explicitly call + # truffle.cmd. Reference: + # https://truffleframework.com/docs/truffle/reference/configuration#resolving-naming-conflicts-on-windows + if not truffle_ignore_compile: + truffle_base_command = "truffle" if platform.system() != 'Windows' else "truffle.cmd" + cmd = [truffle_base_command, 'compile'] + if truffle_version: + cmd = ['npx', truffle_version, 'compile'] + elif os.path.isfile('package.json'): + with open('package.json') as f: + package = json.load(f) + if 'devDependencies' in package: + if 'truffle' in package['devDependencies']: + version = package['devDependencies']['truffle'] + if version.startswith('^'): + version = version[1:] + truffle_version = 'truffle@{}'.format(version) + cmd = ['npx', truffle_version, 'compile'] + logger.info("'{}' running (use --truffle-version truffle@x.x.x to use specific version)".format(' '.join(cmd))) + process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + stdout, stderr = process.communicate() + stdout, stderr = stdout.decode(), stderr.decode()# convert bytestrings to unicode strings + + logger.info(stdout) - def _init_from_truffle(self, contract, build_directory): + if stderr: + logger.error(stderr) if not os.path.isdir(os.path.join(contract, build_directory)): logger.info(red('No truffle build directory found, did you run `truffle compile`?')) sys.exit(-1) diff --git a/utils/possible_paths/__main__.py b/utils/possible_paths/__main__.py index 9b564f804..321f1bcfd 100644 --- a/utils/possible_paths/__main__.py +++ b/utils/possible_paths/__main__.py @@ -36,7 +36,7 @@ def main(): args = parse_args() # Perform slither analysis on the given filename - slither = Slither(args.filename, is_truffle=os.path.isdir(args.filename), solc=args.solc, disable_solc_warnings=True) + slither = Slither(args.filename, solc=args.solc, disable_solc_warnings=True) try: targets = resolve_functions(slither, args.targets) diff --git a/utils/upgradability/__main__.py b/utils/upgradability/__main__.py index d372c2739..069e6f8e8 100644 --- a/utils/upgradability/__main__.py +++ b/utils/upgradability/__main__.py @@ -40,11 +40,11 @@ def main(): args = parse_args() proxy_filename = vars(args)['proxy.sol'] - proxy = Slither(proxy_filename, is_truffle=os.path.isdir(proxy_filename), solc=args.solc, disable_solc_warnings=True) + proxy = Slither(proxy_filename, solc=args.solc, disable_solc_warnings=True) proxy_name = args.ProxyName v1_filename = vars(args)['implem.sol'] - v1 = Slither(v1_filename, is_truffle=os.path.isdir(v1_filename), solc=args.solc, disable_solc_warnings=True) + v1 = Slither(v1_filename, solc=args.solc, disable_solc_warnings=True) v1_name = args.ContractName check_initialization(v1) @@ -53,7 +53,7 @@ def main(): compare_function_ids(v1, v1_name, proxy, proxy_name) compare_variables_order_proxy(v1, v1_name, proxy, proxy_name) else: - v2 = Slither(args.new_version, is_truffle=os.path.isdir(args.new_version), solc=args.solc, disable_solc_warnings=True) + v2 = Slither(args.new_version, solc=args.solc, disable_solc_warnings=True) v2_name = v1_name if not args.new_contract_name else args.new_contract_name check_initialization(v2) compare_function_ids(v2, v2_name, proxy, proxy_name)