diff --git a/examples/printers/call_graph.sol.dot b/examples/printers/call_graph.sol.dot index 5677b7a40..723278180 100644 --- a/examples/printers/call_graph.sol.dot +++ b/examples/printers/call_graph.sol.dot @@ -11,11 +11,11 @@ label = "ContractA" subgraph cluster_63_ContractB { label = "ContractB" "63_my_second_func_b" [label="my_second_func_b"] +"63_my_func_b" [label="my_func_b"] "63_my_func_a" [label="my_func_a"] "63_constructor" [label="constructor"] -"63_my_func_b" [label="my_func_b"] -"63_my_func_b" -> "63_my_second_func_b" "63_my_func_a" -> "63_my_second_func_b" +"63_my_func_b" -> "63_my_second_func_b" } subgraph cluster_solidity { label = "[Solidity]" diff --git a/slither/__main__.py b/slither/__main__.py index 2995c3ddb..0454eb728 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -18,7 +18,7 @@ from slither.detectors.abstract_detector import (AbstractDetector, from slither.printers import all_printers from slither.printers.abstract_printer import AbstractPrinter from slither.slither import Slither -from slither.utils.colors import red, set_colorization_enabled +from slither.utils.colors import red, yellow, set_colorization_enabled from slither.utils.command_line import (output_detectors, output_detectors_json, output_printers, output_to_markdown, output_wiki) @@ -43,7 +43,13 @@ def process(filename, args, detector_classes, printer_classes): ast = '--ast-compact-json' if args.legacy_ast: ast = '--ast-json' - slither = Slither(filename, args.solc, args.disable_solc_warnings, args.solc_args, ast) + slither = Slither(filename, + solc=args.solc, + disable_solc_warnings=args.disable_solc_warnings, + solc_arguments=args.solc_args, + ast_format=ast, + filter_paths=parse_filter_paths(args), + triage_mode=args.triage_mode) return _process(slither, detector_classes, printer_classes) @@ -70,35 +76,39 @@ def _process(slither, detector_classes, printer_classes): return results, analyzed_contracts_count def process_truffle(dirname, args, detector_classes, printer_classes): - cmd = ['truffle', '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) + if not args.ignore_truffle_compile: + cmd = ['truffle', '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, - args.solc, - args.disable_solc_warnings, - args.solc_args, - is_truffle=True) + solc=args.solc, + disable_solc_warnings=args.disable_solc_warnings, + solc_arguments=args.solc_args, + is_truffle=True, + filter_paths=parse_filter_paths(args), + triage_mode=args.triage_mode) + return _process(slither, detector_classes, printer_classes) @@ -110,7 +120,13 @@ def process_files(filenames, args, detector_classes, printer_classes): contract_loaded = json.load(f) all_contracts.append(contract_loaded['ast']) - slither = Slither(all_contracts, args.solc, args.disable_solc_warnings, args.solc_args) + slither = Slither(all_contracts, + solc=args.solc, + disable_solc_warnings=args.disable_solc_warnings, + solc_arguments=args.solc_args, + filter_paths=parse_filter_paths(args), + triage_mode=args.triage_mode) + return _process(slither, detector_classes, printer_classes) # endregion @@ -121,8 +137,11 @@ def process_files(filenames, args, detector_classes, printer_classes): ################################################################################### def output_json(results, filename): - with open(filename, 'w', encoding='utf8') as f: - json.dump(results, f) + if os.path.isfile(filename): + logger.info(yellow(f'{filename} exists already, the overwrite is prevented')) + else: + with open(filename, 'w', encoding='utf8') as f: + json.dump(results, f) # endregion ################################################################################### @@ -181,10 +200,11 @@ def choose_detectors(args, all_detector_classes): if args.detectors_to_run == 'all': detectors_to_run = all_detector_classes - detectors_excluded = args.detectors_to_exclude.split(',') - for d in detectors: - if d in detectors_excluded: - detectors_to_run.remove(detectors[d]) + if args.detectors_to_exclude: + detectors_excluded = args.detectors_to_exclude.split(',') + for d in detectors: + if d in detectors_excluded: + detectors_to_run.remove(detectors[d]) else: for d in args.detectors_to_run.split(','): if d in detectors: @@ -219,7 +239,7 @@ def choose_printers(args, all_printer_classes): printers_to_run = [] # disable default printer - if args.printers_to_run == '': + if args.printers_to_run is None: return [] printers = {p.ARGUMENT: p for p in all_printer_classes} @@ -237,6 +257,31 @@ def choose_printers(args, all_printer_classes): ################################################################################### ################################################################################### +def parse_filter_paths(args): + if args.filter_paths: + return args.filter_paths.split(',') + return [] + +# Those are the flags shared by the command line and the config file +defaults_flag_in_config = { + 'detectors_to_run': 'all', + 'printers_to_run': None, + 'detectors_to_exclude': None, + 'exclude_informational': False, + 'exclude_low': False, + 'exclude_medium': False, + 'exclude_high': False, + 'solc': 'solc', + 'solc_args': None, + 'disable_solc_warnings': False, + 'json': None, + 'truffle_version': None, + 'disable_color': False, + 'filter_paths': None, + 'ignore_truffle_compile': False, + 'legacy_ast': False + } + def parse_args(detector_classes, printer_classes): parser = argparse.ArgumentParser(description='Slither', usage="slither.py contract.sol [flag]") @@ -260,7 +305,7 @@ def parse_args(detector_classes, printer_classes): ', '.join(d.ARGUMENT for d in detector_classes)), action='store', dest='detectors_to_run', - default='all') + default=defaults_flag_in_config['detectors_to_run']) group_printer.add_argument('--print', help='Comma-separated list fo contract information printers, ' @@ -268,7 +313,7 @@ def parse_args(detector_classes, printer_classes): ', '.join(d.ARGUMENT for d in printer_classes)), action='store', dest='printers_to_run', - default='') + default=defaults_flag_in_config['printers_to_run']) group_detector.add_argument('--list-detectors', help='List available detectors', @@ -282,48 +327,46 @@ def parse_args(detector_classes, printer_classes): nargs=0, default=False) - group_detector.add_argument('--exclude', help='Comma-separated list of detectors that should be excluded', action='store', dest='detectors_to_exclude', - default='') + default=defaults_flag_in_config['detectors_to_exclude']) group_detector.add_argument('--exclude-informational', help='Exclude informational impact analyses', action='store_true', - default=False) + default=defaults_flag_in_config['exclude_informational']) group_detector.add_argument('--exclude-low', help='Exclude low impact analyses', action='store_true', - default=False) + default=defaults_flag_in_config['exclude_low']) group_detector.add_argument('--exclude-medium', help='Exclude medium impact analyses', action='store_true', - default=False) + default=defaults_flag_in_config['exclude_medium']) group_detector.add_argument('--exclude-high', help='Exclude high impact analyses', action='store_true', - default=False) - + default=defaults_flag_in_config['exclude_high']) group_solc.add_argument('--solc', help='solc path', action='store', - default='solc') + default=defaults_flag_in_config['solc']) group_solc.add_argument('--solc-args', help='Add custom solc arguments. Example: --solc-args "--allow-path /tmp --evm-version byzantium".', action='store', - default=None) + default=defaults_flag_in_config['solc_args']) group_solc.add_argument('--disable-solc-warnings', help='Disable solc warnings', action='store_true', - default=False) + default=defaults_flag_in_config['disable_solc_warnings']) group_solc.add_argument('--solc-ast', help='Provide the ast solc file', @@ -333,17 +376,40 @@ def parse_args(detector_classes, printer_classes): group_misc.add_argument('--json', help='Export results as JSON', action='store', - default=None) + default=defaults_flag_in_config['json']) group_misc.add_argument('--truffle-version', help='Use a local Truffle version (with npx)', action='store', - default=False) + default=defaults_flag_in_config['truffle_version']) + group_misc.add_argument('--disable-color', help='Disable output colorization', action='store_true', - default=False) + default=defaults_flag_in_config['disable_color']) + group_misc.add_argument('--filter-paths', + help='Comma-separated list of paths for which results will be excluded', + action='store', + 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('--triage-mode', + help='Run triage mode (save results in slither.db.json)', + action='store_true', + dest='triage_mode', + default=False) + + group_misc.add_argument('--config-file', + help='Provide a config file (default: slither.config.json)', + action='store', + dest='config_file', + default='slither.config.json') # debugger command parser.add_argument('--debug', @@ -370,7 +436,7 @@ def parse_args(detector_classes, printer_classes): parser.add_argument('--legacy-ast', help=argparse.SUPPRESS, action='store_true', - default=False) + default=defaults_flag_in_config['legacy_ast']) # if the json is splitted in different files parser.add_argument('--splitted', @@ -384,6 +450,19 @@ def parse_args(detector_classes, printer_classes): args = parser.parse_args() + if os.path.isfile(args.config_file): + try: + with open(args.config_file) as f: + config = json.load(f) + for key, elem in config.items(): + if key not in defaults_flag_in_config: + logger.info(yellow('{} has an unknown key: {} : {}'.format(args.config_file, key, elem))) + continue + if getattr(args, key) == defaults_flag_in_config[key]: + setattr(args, key, elem) + except json.decoder.JSONDecodeError as e: + logger.error(red('Impossible to read {}, please check the file {}'.format(args.config_file, e))) + return args class ListDetectors(argparse.Action): diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index 31714d46b..cb848d819 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -2,8 +2,14 @@ Main module """ import os +import logging +import json from slither.core.context.context import Context from slither.slithir.operations import InternalCall +from slither.utils.colors import red + +logger = logging.getLogger("Slither") +logging.basicConfig() class Slither(Context): """ @@ -22,10 +28,62 @@ class Slither(Context): self._all_functions = set() self._all_modifiers = set() + self._previous_results_filename = 'slither.db.json' + self._results_to_hide = [] + self._previous_results = [] + self._paths_to_filter = set() + + + ################################################################################### + ################################################################################### + # region Source code + ################################################################################### + ################################################################################### + + @property + def source_code(self): + """ {filename: source_code}: source code """ + return self._raw_source_code + @property def source_units(self): return self._source_units + @property + def filename(self): + """str: Filename.""" + return self._filename + + # endregion + ################################################################################### + ################################################################################### + # region Pragma attributes + ################################################################################### + ################################################################################### + + @property + def solc_version(self): + """str: Solidity version.""" + return self._solc_version + + @property + def pragma_directives(self): + """ list(list(str)): Pragma directives. Example [['solidity', '^', '0.4', '.24']]""" + return self._pragma_directives + + @property + def import_directives(self): + """ list(str): Import directives""" + return self._import_directives + + + # endregion + ################################################################################### + ################################################################################### + # region Contracts + ################################################################################### + ################################################################################### + @property def contracts(self): """list(Contract): List of contracts.""" @@ -42,6 +100,23 @@ class Slither(Context): """list(dict(str: Contract): List of contracts as dict: name -> Contract.""" return self._contracts + def get_contract_from_name(self, contract_name): + """ + Return a contract from a name + Args: + contract_name (str): name of the contract + Returns: + Contract + """ + return next((c for c in self.contracts if c.name == contract_name), None) + + # endregion + ################################################################################### + ################################################################################### + # region Functions and modifiers + ################################################################################### + ################################################################################### + @property def functions(self): return list(self._all_functions) @@ -60,31 +135,6 @@ class Slither(Context): def functions_and_modifiers(self): return self.functions + self.modifiers - @property - def filename(self): - """str: Filename.""" - return self._filename - - @property - def solc_version(self): - """str: Solidity version.""" - return self._solc_version - - @property - def pragma_directives(self): - """ list(list(str)): Pragma directives. Example [['solidity', '^', '0.4', '.24']]""" - return self._pragma_directives - - @property - def import_directives(self): - """ list(str): Import directives""" - return self._import_directives - - @property - def source_code(self): - """ {filename: source_code}: source code """ - return self._raw_source_code - def _propagate_function_calls(self): for f in self.functions_and_modifiers: for node in f.nodes: @@ -92,15 +142,13 @@ class Slither(Context): if isinstance(ir, InternalCall): ir.function.add_reachable_from_node(node, ir) - def get_contract_from_name(self, contract_name): - """ - Return a contract from a name - Args: - contract_name (str): name of the contract - Returns: - Contract - """ - return next((c for c in self.contracts if c.name == contract_name), None) + + # endregion + ################################################################################### + ################################################################################### + # region Export + ################################################################################### + ################################################################################### def print_functions(self, d): """ @@ -109,3 +157,50 @@ class Slither(Context): for c in self.contracts: for f in c.functions: f.cfg_to_dot(os.path.join(d, '{}.{}.dot'.format(c.name, f.name))) + + # endregion + ################################################################################### + ################################################################################### + # region Filtering results + ################################################################################### + ################################################################################### + + def valid_result(self, r): + ''' + Check if the result is valid + A result is invalid if: + - All its source paths belong to the source path filtered + - Or a similar result was reported and saved during a previous run + ''' + if r['elements'] and all((any(path in elem['source_mapping']['filename'] for path in self._paths_to_filter) for elem in r['elements'])): + return False + return not r['description'] in [pr['description'] for pr in self._previous_results] + + def load_previous_results(self): + filename = self._previous_results_filename + try: + if os.path.isfile(filename): + with open(filename) as f: + self._previous_results = json.load(f) + except json.decoder.JSONDecodeError: + logger.error(red('Impossible to decode {}. Consider removing the file'.format(filename))) + + def write_results_to_hide(self): + if not self._results_to_hide: + return + filename = self._previous_results_filename + with open(filename, 'w', encoding='utf8') as f: + results = self._results_to_hide + self._previous_results + json.dump(results, f) + + def save_results_to_hide(self, results): + self._results_to_hide += results + + def add_path_to_filter(self, path): + ''' + Add path to filter + Path are used through direct comparison (no regex) + ''' + self._paths_to_filter.add(path) + + # endregion diff --git a/slither/detectors/abstract_detector.py b/slither/detectors/abstract_detector.py index aff8840a6..d9caf0d92 100644 --- a/slither/detectors/abstract_detector.py +++ b/slither/detectors/abstract_detector.py @@ -86,18 +86,54 @@ class AbstractDetector(metaclass=abc.ABCMeta): DetectorClassification.INFORMATIONAL]: raise IncorrectDetectorInitialization('CONFIDENCE is not initialized {}'.format(self.__class__.__name__)) - def log(self, info): - if self.logger: - info = "\n"+info - if self.WIKI != '': - info += 'Reference: {}'.format(self.WIKI) - self.logger.info(self.color(info)) +# def log(self, info): +# if self.logger: +# info = "\n"+info +# if self.WIKI != '': +# info += 'Reference: {}'.format(self.WIKI) +# self.logger.info(self.color(info)) + + def _log(self, info): + self.logger.info(self.color(info)) @abc.abstractmethod - def detect(self): + def _detect(self): """TODO Documentation""" return + def detect(self): + results = self._detect() + results = [r for r in results if self.slither.valid_result(r)] + if results: + if self.logger: + info = '\n' + for idx, result in enumerate(results): + if self.slither.triage_mode: + info += '{}: '.format(idx) + info += result['description'] + info += 'Reference: {}'.format(self.WIKI) + self._log(info) + if results and self.slither.triage_mode: + while True: + indexes = input('Results to hide during next runs: "0,1,..." or "All" (enter to not hide results): '.format(len(results))) + if indexes == 'All': + self.slither.save_results_to_hide(results) + return [] + if indexes == '': + return results + if indexes.startswith('['): + indexes = indexes[1:] + if indexes.endswith(']'): + indexes = indexes[:-1] + try: + indexes = [int(i) for i in indexes.split(',')] + self.slither.save_results_to_hide([r for (idx, r) in enumerate(results) if idx in indexes]) + return [r for (idx, r) in enumerate(results) if idx not in indexes] + except ValueError: + self.logger.error(yellow('Malformed input. Example of valid input: 0,1,2,3')) + return results + + @property def color(self): return classification_colors[self.IMPACT] diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 2b77a52d7..8134d7059 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -28,5 +28,5 @@ from .shadowing.builtin_symbols import BuiltinSymbolShadowing from .operations.block_timestamp import Timestamp from .statements.calls_in_loop import MultipleCallsInLoop from .statements.incorrect_strict_equality import IncorrectStrictEquality - - +# +# diff --git a/slither/detectors/attributes/const_functions.py b/slither/detectors/attributes/const_functions.py index 26ac42a20..d454b5827 100644 --- a/slither/detectors/attributes/const_functions.py +++ b/slither/detectors/attributes/const_functions.py @@ -41,7 +41,7 @@ All the calls to `get` reverts, breaking Bob's smart contract execution.''' WIKI_RECOMMENDATION = 'Ensure that the attributes of contracts compiled prior Solidity 0.5.0 are correct.' - def detect(self): + def _detect(self): """ Detect the constant function changing the state Recursively visit the calls @@ -58,7 +58,6 @@ All the calls to `get` reverts, breaking Bob's smart contract execution.''' attr = 'view' if f.view else 'pure' info = '{}.{} ({}) is declared {} but contains assembly code\n' info = info.format(f.contract.name, f.name, f.source_mapping_str, attr) - self.log(info) json = self.generate_json_result(info) self.add_function_to_json(f, json) json['elements'] = [{'type': 'info', @@ -73,7 +72,6 @@ All the calls to `get` reverts, breaking Bob's smart contract execution.''' for variable_written in variables_written: info += '\t- {}.{}\n'.format(variable_written.contract.name, variable_written.name) - self.log(info) json = self.generate_json_result(info) diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index eef49ec4e..445df441c 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -22,7 +22,7 @@ class ConstantPragma(AbstractDetector): WIKI_DESCRIPTION = 'Detect if different Solidity versions are used.' WIKI_RECOMMENDATION = 'Use one Solidity version.' - def detect(self): + def _detect(self): results = [] pragma = self.slither.pragma_directives versions = [p.version for p in pragma] @@ -33,7 +33,6 @@ class ConstantPragma(AbstractDetector): info += "\t- Version used: {}\n".format([str(v) for v in versions]) for p in pragma: info += "\t- {} declares {}\n".format(p.source_mapping_str, str(p)) - self.log(info) json = self.generate_json_result(info) # follow the same format than add_nodes_to_json diff --git a/slither/detectors/attributes/incorrect_solc.py b/slither/detectors/attributes/incorrect_solc.py index 64a581ce8..724aaacec 100644 --- a/slither/detectors/attributes/incorrect_solc.py +++ b/slither/detectors/attributes/incorrect_solc.py @@ -62,7 +62,7 @@ We recommend avoiding complex pragma statement.''' return self._check_version(version_left) else: return self.COMPLEX_PRAGMA - def detect(self): + def _detect(self): """ Detects pragma statements that allow for outdated solc versions. :return: Returns the relevant JSON data for the findings. @@ -88,7 +88,6 @@ We recommend avoiding complex pragma statement.''' info = "Detected issues with version pragma in {}:\n".format(self.filename) for (reason, p) in disallowed_pragmas: info += "\t- {} ({}): {}\n".format(p, p.source_mapping_str, reason) - self.log(info) json = self.generate_json_result(info) diff --git a/slither/detectors/attributes/locked_ether.py b/slither/detectors/attributes/locked_ether.py index d7aa73dd2..bde8f67cf 100644 --- a/slither/detectors/attributes/locked_ether.py +++ b/slither/detectors/attributes/locked_ether.py @@ -65,7 +65,7 @@ Every ethers send to `Locked` will be lost.''' return True - def detect(self): + def _detect(self): results = [] for contract in self.slither.contracts_derived: @@ -82,7 +82,6 @@ Every ethers send to `Locked` will be lost.''' info = txt.format(self.filename, contract.name, [f.name for f in funcs_payable]) - self.log(info) json = self.generate_json_result(info) self.add_functions_to_json(funcs_payable, json) diff --git a/slither/detectors/examples/backdoor.py b/slither/detectors/examples/backdoor.py index a1c6d6c54..bbc60a8b3 100644 --- a/slither/detectors/examples/backdoor.py +++ b/slither/detectors/examples/backdoor.py @@ -18,7 +18,7 @@ class Backdoor(AbstractDetector): WIKI_EXPLOIT_SCENARIO = '..' WIKI_RECOMMENDATION = '..' - def detect(self): + def _detect(self): results = [] for contract in self.slither.contracts_derived: @@ -28,12 +28,9 @@ class Backdoor(AbstractDetector): # Info to be printed info = 'Backdoor function found in {}.{} ({})\n' info = info.format(contract.name, f.name, f.source_mapping_str) - # Print the info - self.log(info) # Add the result in result json = self.generate_json_result(info) self.add_function_to_json(f, json) results.append(json) - return results diff --git a/slither/detectors/functions/arbitrary_send.py b/slither/detectors/functions/arbitrary_send.py index bba2ce535..a44eefd42 100644 --- a/slither/detectors/functions/arbitrary_send.py +++ b/slither/detectors/functions/arbitrary_send.py @@ -100,7 +100,7 @@ Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract ret.append((f, nodes)) return ret - def detect(self): + def _detect(self): """ """ results = [] @@ -117,9 +117,6 @@ Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract for node in nodes: info += '\t- {} ({})\n'.format(node.expression, node.source_mapping_str) - self.log(info) - - json = self.generate_json_result(info) self.add_function_to_json(func, json) self.add_nodes_to_json(nodes, json) diff --git a/slither/detectors/functions/external_function.py b/slither/detectors/functions/external_function.py index 11cafdfad..a9a456b4c 100644 --- a/slither/detectors/functions/external_function.py +++ b/slither/detectors/functions/external_function.py @@ -96,11 +96,9 @@ class ExternalFunction(AbstractDetector): for function in derived_contract.functions if function.full_name == base_most_function.full_name] - def detect(self): + def _detect(self): results = [] - all_info = '' - # Create a set to track contracts with dynamic calls. All contracts with dynamic calls could potentially be # calling functions internally, and thus we can't assume any function in such contracts isn't called by them. dynamic_call_contracts = set() @@ -171,12 +169,9 @@ class ExternalFunction(AbstractDetector): info = txt.format(function_definition.contract.name, function_definition.name, function_definition.source_mapping_str) - all_info += info json = self.generate_json_result(info) self.add_function_to_json(function_definition, json) results.append(json) - if all_info != '': - self.log(all_info) return results diff --git a/slither/detectors/functions/suicidal.py b/slither/detectors/functions/suicidal.py index 466cc535c..501952f6b 100644 --- a/slither/detectors/functions/suicidal.py +++ b/slither/detectors/functions/suicidal.py @@ -64,7 +64,7 @@ Bob calls `kill` and destruct the contract.''' ret.append(f) return ret - def detect(self): + def _detect(self): """ Detect the suicidal functions """ results = [] @@ -77,8 +77,6 @@ Bob calls `kill` and destruct the contract.''' func.name, func.source_mapping_str) - self.log(info) - json = self.generate_json_result(info) self.add_function_to_json(func, json) results.append(json) diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 5d2b873ac..a6b5d72f3 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -51,16 +51,14 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 def should_avoid_name(name): return re.search('^[lOI]$', name) is not None - def detect(self): + def _detect(self): results = [] - all_info = '' for contract in self.contracts: if not self.is_cap_words(contract.name): info = "Contract '{}' ({}) is not in CapWords\n".format(contract.name, contract.source_mapping_str) - all_info += info json = self.generate_json_result(info) elem = dict() @@ -78,7 +76,6 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 if not self.is_cap_words(struct.name): info = "Struct '{}.{}' ({}) is not in CapWords\n" info = info.format(struct.contract.name, struct.name, struct.source_mapping_str) - all_info += info json = self.generate_json_result(info) elem = dict() @@ -95,7 +92,6 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 if not self.is_cap_words(event.name): info = "Event '{}.{}' ({}) is not in CapWords\n" info = info.format(event.contract.name, event.name, event.source_mapping_str) - all_info += info json = self.generate_json_result(info) elem = dict() @@ -113,7 +109,6 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 if not self.is_mixed_case(func.name): info = "Function '{}.{}' ({}) is not in mixedCase\n" info = info.format(func.contract.name, func.name, func.source_mapping_str) - all_info += info json = self.generate_json_result(info) elem = dict() @@ -135,7 +130,6 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 argument.function.contract.name, argument.function, argument.source_mapping_str) - all_info += info json = self.generate_json_result(info) elem = dict() @@ -154,7 +148,6 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 if not self.is_upper_case_with_underscores(var.name): info = "Variable '{}.{}' ({}) used l, O, I, which should not be used\n" info = info.format(var.contract.name, var.name, var.source_mapping_str) - all_info += info json = self.generate_json_result(info) elem = dict() @@ -173,7 +166,6 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 if not self.is_upper_case_with_underscores(var.name): info = "Constant '{}.{}' ({}) is not in UPPER_CASE_WITH_UNDERSCORES\n" info = info.format(var.contract.name, var.name, var.source_mapping_str) - all_info += info json = self.generate_json_result(info) elem = dict() @@ -192,7 +184,6 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 if not correct_naming: info = "Variable '{}.{}' ({}) is not in mixedCase\n" info = info.format(var.contract.name, var.name, var.source_mapping_str) - all_info += info json = self.generate_json_result(info) elem = dict() @@ -210,7 +201,6 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 if not self.is_cap_words(enum.name): info = "Enum '{}.{}' ({}) is not in CapWords\n" info = info.format(enum.contract.name, enum.name, enum.source_mapping_str) - all_info += info json = self.generate_json_result(info) elem = dict() @@ -231,7 +221,6 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 info = info.format(modifier.contract.name, modifier.name, modifier.source_mapping_str) - all_info += info json = self.generate_json_result(info) elem = dict() @@ -242,7 +231,5 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2 json['elements'] = [elem] results.append(json) - if all_info != '': - self.log(all_info) return results diff --git a/slither/detectors/operations/block_timestamp.py b/slither/detectors/operations/block_timestamp.py index fcb7d7b50..7c2f61a6b 100644 --- a/slither/detectors/operations/block_timestamp.py +++ b/slither/detectors/operations/block_timestamp.py @@ -67,7 +67,7 @@ class Timestamp(AbstractDetector): ret.append((f, nodes)) return ret - def detect(self): + def _detect(self): """ """ results = [] @@ -84,9 +84,6 @@ class Timestamp(AbstractDetector): for node in nodes: info += '\t- {} ({})\n'.format(node.expression, node.source_mapping_str) - self.log(info) - - json = self.generate_json_result(info) self.add_function_to_json(func, json) self.add_nodes_to_json(nodes, json) diff --git a/slither/detectors/operations/low_level_calls.py b/slither/detectors/operations/low_level_calls.py index 57451d46b..6425f087a 100644 --- a/slither/detectors/operations/low_level_calls.py +++ b/slither/detectors/operations/low_level_calls.py @@ -41,11 +41,10 @@ class LowLevelCalls(AbstractDetector): ret.append((f, assembly_nodes)) return ret - def detect(self): + def _detect(self): """ Detect the functions that use low level calls """ results = [] - all_info = '' for c in self.contracts: values = self.detect_low_level_calls(c) for func, nodes in values: @@ -53,15 +52,10 @@ class LowLevelCalls(AbstractDetector): info = info.format(func.contract.name, func.name, func.source_mapping_str) for node in nodes: info += "\t-{} {}\n".format(str(node.expression), node.source_mapping_str) - all_info += info json = self.generate_json_result(info) self.add_function_to_json(func, json) self.add_nodes_to_json(nodes, json) results.append(json) - - - if all_info != '': - self.log(all_info) return results diff --git a/slither/detectors/operations/unused_return_values.py b/slither/detectors/operations/unused_return_values.py index b48921b9e..3d8f8777b 100644 --- a/slither/detectors/operations/unused_return_values.py +++ b/slither/detectors/operations/unused_return_values.py @@ -58,7 +58,7 @@ contract MyConc{ return [nodes_origin[value].node for value in values_returned] - def detect(self): + def _detect(self): """ Detect unused high level calls that return a value but are never used """ results = [] @@ -74,7 +74,6 @@ contract MyConc{ f.source_mapping_str) for node in unused_return: info += "\t-{} ({})\n".format(node.expression, node.source_mapping_str) - self.log(info) json = self.generate_json_result(info) self.add_function_to_json(f, json) diff --git a/slither/detectors/reentrancy/reentrancy.py b/slither/detectors/reentrancy/reentrancy.py index ace071469..527e80273 100644 --- a/slither/detectors/reentrancy/reentrancy.py +++ b/slither/detectors/reentrancy/reentrancy.py @@ -188,7 +188,7 @@ class Reentrancy(AbstractDetector): self._explore(function.entry_point, []) function.context[self.KEY] = True - def detect(self): + def _detect(self): """ """ # if a node was already visited by another path diff --git a/slither/detectors/reentrancy/reentrancy_benign.py b/slither/detectors/reentrancy/reentrancy_benign.py index 719512a9f..0ca742dc6 100644 --- a/slither/detectors/reentrancy/reentrancy_benign.py +++ b/slither/detectors/reentrancy/reentrancy_benign.py @@ -71,11 +71,11 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr result[finding_key] = list(set(result[finding_key] + finding_vars)) return result - def detect(self): + def _detect(self): """ """ - super().detect() + super()._detect() reentrancies = self.find_reentrancies() results = [] @@ -96,7 +96,6 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr info += '\tState variables written after the call(s):\n' for (v, node) in varsWritten: info += '\t- {} ({})\n'.format(v, node.source_mapping_str) - self.log(info) sending_eth_json = [] if calls != send_eth: diff --git a/slither/detectors/reentrancy/reentrancy_eth.py b/slither/detectors/reentrancy/reentrancy_eth.py index 515fb3c46..8a43370b1 100644 --- a/slither/detectors/reentrancy/reentrancy_eth.py +++ b/slither/detectors/reentrancy/reentrancy_eth.py @@ -73,10 +73,10 @@ Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw m result[finding_key] = list(set(result[finding_key] + finding_vars)) return result - def detect(self): + def _detect(self): """ """ - super().detect() + super()._detect() reentrancies = self.find_reentrancies() @@ -102,7 +102,6 @@ Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw m info += '\tState variables written after the call(s):\n' for (v, node) in varsWritten: info += '\t- {} ({})\n'.format(v, node.source_mapping_str) - self.log(info) sending_eth_json = [] if calls != send_eth: diff --git a/slither/detectors/reentrancy/reentrancy_read_before_write.py b/slither/detectors/reentrancy/reentrancy_read_before_write.py index 92c1fb147..38b75eea6 100644 --- a/slither/detectors/reentrancy/reentrancy_read_before_write.py +++ b/slither/detectors/reentrancy/reentrancy_read_before_write.py @@ -70,11 +70,11 @@ Do not report reentrancies that involve ethers (see `reentrancy-eth`)''' result[finding_key] = list(set(result[finding_key] + finding_vars)) return result - def detect(self): + def _detect(self): """ """ - super().detect() + super()._detect() reentrancies = self.find_reentrancies() results = [] @@ -90,7 +90,6 @@ Do not report reentrancies that involve ethers (see `reentrancy-eth`)''' info += '\tState variables written after the call(s):\n' for (v, node) in varsWritten: info += '\t- {} ({})\n'.format(v, node.source_mapping_str) - self.log(info) sending_eth_json = [] diff --git a/slither/detectors/shadowing/abstract.py b/slither/detectors/shadowing/abstract.py index 5115a3f3f..1b31b0c71 100644 --- a/slither/detectors/shadowing/abstract.py +++ b/slither/detectors/shadowing/abstract.py @@ -50,7 +50,7 @@ contract DerivedContract is BaseContract{ return ret - def detect(self): + def _detect(self): """ Detect shadowing Recursively visit the calls @@ -72,7 +72,6 @@ contract DerivedContract is BaseContract{ info += "\t- {}.{} ({})\n".format(var.contract.name, var.name, var.source_mapping_str) - self.log(info) json = self.generate_json_result(info) self.add_variables_to_json(all_variables, json) diff --git a/slither/detectors/shadowing/builtin_symbols.py b/slither/detectors/shadowing/builtin_symbols.py index 2cb4c4471..fca17ad9b 100644 --- a/slither/detectors/shadowing/builtin_symbols.py +++ b/slither/detectors/shadowing/builtin_symbols.py @@ -111,7 +111,7 @@ contract Bug { return result - def detect(self): + def _detect(self): """ Detect shadowing of built-in symbols Recursively visit the calls @@ -140,8 +140,6 @@ contract Bug { shadow_type, shadow_object.source_mapping_str, shadow_object.name) - # Print relevant information to the log - self.log(info) # Generate relevant JSON data for this shadowing definition. json = self.generate_json_result(info) diff --git a/slither/detectors/shadowing/local.py b/slither/detectors/shadowing/local.py index 4dfbb3443..c8350af39 100644 --- a/slither/detectors/shadowing/local.py +++ b/slither/detectors/shadowing/local.py @@ -89,7 +89,7 @@ contract Bug { return result - def detect(self): + def _detect(self): """ Detect shadowing local variables Recursively visit the calls @@ -116,8 +116,6 @@ contract Bug { overshadowed_entry[0], overshadowed_entry[2].source_mapping_str) - # Print relevant information to the log - self.log(info) # Generate relevant JSON data for this shadowing definition. json = self.generate_json_result(info) diff --git a/slither/detectors/shadowing/state.py b/slither/detectors/shadowing/state.py index 096d5107f..0fbb477bf 100644 --- a/slither/detectors/shadowing/state.py +++ b/slither/detectors/shadowing/state.py @@ -61,7 +61,7 @@ contract DerivedContract is BaseContract{ ret.append([var] + shadow) return ret - def detect(self): + def _detect(self): """ Detect shadowing Recursively visit the calls @@ -83,7 +83,6 @@ contract DerivedContract is BaseContract{ info += "\t- {}.{} ({})\n".format(var.contract.name, var.name, var.source_mapping_str) - self.log(info) json = self.generate_json_result(info) self.add_variables_to_json(all_variables, json) diff --git a/slither/detectors/statements/assembly.py b/slither/detectors/statements/assembly.py index b770456bc..df63f0df5 100644 --- a/slither/detectors/statements/assembly.py +++ b/slither/detectors/statements/assembly.py @@ -44,11 +44,10 @@ class Assembly(AbstractDetector): ret.append((f, assembly_nodes)) return ret - def detect(self): + def _detect(self): """ Detect the functions that use inline assembly """ results = [] - all_info = '' for c in self.contracts: values = self.detect_assembly(c) for func, nodes in values: @@ -58,13 +57,9 @@ class Assembly(AbstractDetector): for node in nodes: info += "\t- {}\n".format(node.source_mapping_str) - all_info += info - json = self.generate_json_result(info) self.add_function_to_json(func, json) self.add_nodes_to_json(nodes, json) results.append(json) - if all_info != '': - self.log(all_info) return results diff --git a/slither/detectors/statements/calls_in_loop.py b/slither/detectors/statements/calls_in_loop.py index 2e5dbf9c7..8767d7ce1 100644 --- a/slither/detectors/statements/calls_in_loop.py +++ b/slither/detectors/statements/calls_in_loop.py @@ -78,7 +78,7 @@ If one of the destinations has a fallback function which reverts, `bad` will alw return ret - def detect(self): + def _detect(self): """ """ results = [] @@ -91,8 +91,6 @@ If one of the destinations has a fallback function which reverts, `bad` will alw info += "\t- {} ({})\n".format(node.expression, node.source_mapping_str) - self.log(info) - json = self.generate_json_result(info) self.add_function_to_json(func, json) self.add_nodes_to_json([node], json) diff --git a/slither/detectors/statements/controlled_delegatecall.py b/slither/detectors/statements/controlled_delegatecall.py index e1feadab9..900fe0915 100644 --- a/slither/detectors/statements/controlled_delegatecall.py +++ b/slither/detectors/statements/controlled_delegatecall.py @@ -37,7 +37,7 @@ Bob calls `delegate` and delegate the execution to its malicious contract. As a ret.append(node) return ret - def detect(self): + def _detect(self): results = [] for contract in self.slither.contracts: @@ -50,7 +50,6 @@ Bob calls `delegate` and delegate the execution to its malicious contract. As a info = info.format(contract.name, f.name, f.source_mapping_str) for node in nodes: info += '\t{} ({})\n'.format(node.expression, node.source_mapping_str) - self.log(info) json = self.generate_json_result(info) self.add_function_to_json(f, json) diff --git a/slither/detectors/statements/incorrect_strict_equality.py b/slither/detectors/statements/incorrect_strict_equality.py index 786f7d650..fe2ae895b 100644 --- a/slither/detectors/statements/incorrect_strict_equality.py +++ b/slither/detectors/statements/incorrect_strict_equality.py @@ -102,7 +102,7 @@ contract Crowdsale{ return results - def detect(self): + def _detect(self): results = [] for c in self.slither.contracts_derived: @@ -126,7 +126,4 @@ contract Crowdsale{ self.add_nodes_to_json(nodes, json) results.append(json) - if info: - self.log(info) - return results diff --git a/slither/detectors/statements/tx_origin.py b/slither/detectors/statements/tx_origin.py index e4f01e764..3f1c938cd 100644 --- a/slither/detectors/statements/tx_origin.py +++ b/slither/detectors/statements/tx_origin.py @@ -58,7 +58,7 @@ Bob is the owner of `TxOrigin`. Bob calls Eve's contract. Eve's contact calls `T ret.append((f, bad_tx_nodes)) return ret - def detect(self): + def _detect(self): """ Detect the functions that use tx.origin in a conditional node """ results = [] @@ -71,8 +71,6 @@ Bob is the owner of `TxOrigin`. Bob calls Eve's contract. Eve's contact calls `T for node in nodes: info += "\t- {} ({})\n".format(node.expression, node.source_mapping_str) - self.log(info) - json = self.generate_json_result(info) self.add_function_to_json(func, json) self.add_nodes_to_json(nodes, json) diff --git a/slither/detectors/variables/possible_const_state_variables.py b/slither/detectors/variables/possible_const_state_variables.py index d46912374..9cdc485aa 100644 --- a/slither/detectors/variables/possible_const_state_variables.py +++ b/slither/detectors/variables/possible_const_state_variables.py @@ -63,7 +63,7 @@ class ConstCandidateStateVars(AbstractDetector): - def detect(self): + def _detect(self): """ Detect state variables that could be const """ results = [] @@ -93,5 +93,4 @@ class ConstCandidateStateVars(AbstractDetector): json = self.generate_json_result(all_info) self.add_variables_to_json(constable_variables, json) results.append(json) - self.log(all_info) return results diff --git a/slither/detectors/variables/uninitialized_local_variables.py b/slither/detectors/variables/uninitialized_local_variables.py index 36d9e2bf7..78e76dfcc 100644 --- a/slither/detectors/variables/uninitialized_local_variables.py +++ b/slither/detectors/variables/uninitialized_local_variables.py @@ -76,7 +76,7 @@ Bob calls `transfer`. As a result, the ethers are sent to the address 0x0 and ar self._detect_uninitialized(function, son, visited) - def detect(self): + def _detect(self): """ Detect uninitialized state variables Recursively visit the calls @@ -107,7 +107,6 @@ Bob calls `transfer`. As a result, the ethers are sent to the address 0x0 and ar function.name, uninitialized_local_variable.source_mapping_str) - self.log(info) json = self.generate_json_result(info) self.add_variable_to_json(uninitialized_local_variable, json) diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index 35a8bd552..b9dc9ceb3 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -81,7 +81,7 @@ Initialize all the variables. If a variable is meant to be initialized to zero, not variable.expression and\ variable in read_variables] - def detect(self): + def _detect(self): """ Detect uninitialized state variables Recursively visit the calls @@ -98,7 +98,6 @@ Initialize all the variables. If a variable is meant to be initialized to zero, variable.source_mapping_str) for f in functions: info += "\t- {} ({})\n".format(f.name, f.source_mapping_str) - self.log(info) source = [variable.source_mapping] source += [f.source_mapping for f in functions] diff --git a/slither/detectors/variables/uninitialized_storage_variables.py b/slither/detectors/variables/uninitialized_storage_variables.py index b0d8930d2..914f5c4d6 100644 --- a/slither/detectors/variables/uninitialized_storage_variables.py +++ b/slither/detectors/variables/uninitialized_storage_variables.py @@ -83,7 +83,7 @@ Bob calls `func`. As a result, `owner` is override to 0. self._detect_uninitialized(function, son, visited) - def detect(self): + def _detect(self): """ Detect uninitialized state variables Recursively visit the calls @@ -108,9 +108,6 @@ Bob calls `func`. As a result, `owner` is override to 0. info = "{} in {}.{} ({}) is a storage variable never initialiazed\n" info = info.format(var_name, function.contract.name, function.name, uninitialized_storage_variable.source_mapping_str) - self.log(info) - - json = self.generate_json_result(info) self.add_variable_to_json(uninitialized_storage_variable, json) diff --git a/slither/detectors/variables/unused_state_variables.py b/slither/detectors/variables/unused_state_variables.py index 7705b75ed..c3c716b1b 100644 --- a/slither/detectors/variables/unused_state_variables.py +++ b/slither/detectors/variables/unused_state_variables.py @@ -48,11 +48,10 @@ class UnusedStateVars(AbstractDetector): return [x for x in contract.variables if x not in variables_used and x.visibility != 'public'] - def detect(self): + def _detect(self): """ Detect unused state variables """ results = [] - all_info = '' for c in self.slither.contracts_derived: unusedVars = self.detect_unused(c) if unusedVars: @@ -63,12 +62,9 @@ class UnusedStateVars(AbstractDetector): var.source_mapping_str, c.name) - all_info += info json = self.generate_json_result(info) self.add_variables_to_json(unusedVars, json) results.append(json) - if all_info != '': - self.log(all_info) return results diff --git a/slither/slither.py b/slither/slither.py index 685b1db6f..32ce70928 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -19,47 +19,86 @@ logger_printer = logging.getLogger("Printers") class Slither(SlitherSolc): - def __init__(self, contract, solc='solc', disable_solc_warnings=False, solc_arguments='', ast_format='--ast-compact-json', is_truffle=False): - self._detectors = [] - self._printers = [] + def __init__(self, contract, **kwargs): + ''' + Args: + contract (str| list(json)) + Keyword Args: + solc (str): solc binary location (default 'solc') + 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) + 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 directory if is_truffle: - if not os.path.isdir(os.path.join(contract, 'build'))\ - or not os.path.isdir(os.path.join(contract, 'build', 'contracts')): - logger.info(red('No truffle build directory found, did you run `truffle compile`?')) - sys.exit(-1) - super(Slither, self).__init__('') - filenames = glob.glob(os.path.join(contract, 'build', 'contracts', '*.json')) - for filename in filenames: - with open(filename, encoding='utf8') as f: - contract_loaded = json.load(f) - contract_loaded = contract_loaded['ast'] - if 'absolutePath' in contract_loaded: - path = contract_loaded['absolutePath'] - else: - path = contract_loaded['attributes']['absolutePath'] - self._parse_contracts_from_loaded_json(contract_loaded, path) - + self._init_from_truffle(contract) # list of files provided (see --splitted option) elif isinstance(contract, list): - super(Slither, self).__init__('') - for c in contract: - if 'absolutePath' in c: - path = c['absolutePath'] - else: - path = c['attributes']['absolutePath'] - self._parse_contracts_from_loaded_json(c, path) + self._init_from_list(contract) # .json or .sol provided else: - contracts_json = self._run_solc(contract, solc, disable_solc_warnings, solc_arguments, ast_format) - super(Slither, self).__init__(contract) + self._init_from_solc(contract, **kwargs) + + self._detectors = [] + self._printers = [] + + filter_paths = kwargs.get('filter_paths', []) + for p in filter_paths: + self.add_path_to_filter(p) - for c in contracts_json: - self._parse_contracts_from_json(c) + triage_mode = kwargs.get('triage_mode', False) + self._triage_mode = triage_mode self._analyze_contracts() + def _init_from_truffle(self, contract): + if not os.path.isdir(os.path.join(contract, 'build'))\ + or not os.path.isdir(os.path.join(contract, 'build', 'contracts')): + logger.info(red('No truffle build directory found, did you run `truffle compile`?')) + sys.exit(-1) + super(Slither, self).__init__('') + filenames = glob.glob(os.path.join(contract, 'build', 'contracts', '*.json')) + for filename in filenames: + with open(filename, encoding='utf8') as f: + contract_loaded = json.load(f) + contract_loaded = contract_loaded['ast'] + if 'absolutePath' in contract_loaded: + path = contract_loaded['absolutePath'] + else: + path = contract_loaded['attributes']['absolutePath'] + self._parse_contracts_from_loaded_json(contract_loaded, path) + + def _init_from_solc(self, contract, **kwargs): + solc = kwargs.get('solc', 'solc') + disable_solc_warnings = kwargs.get('disable_solc_warnings', False) + solc_arguments = kwargs.get('solc_arguments', '') + ast_format = kwargs.get('ast_format', '--ast-compact-json') + + contracts_json = self._run_solc(contract, + solc, + disable_solc_warnings, + solc_arguments, + ast_format) + super(Slither, self).__init__(contract) + + for c in contracts_json: + self._parse_contracts_from_json(c) + + def _init_from_list(self, contract): + super(Slither, self).__init__('') + for c in contract: + if 'absolutePath' in c: + path = c['absolutePath'] + else: + path = c['attributes']['absolutePath'] + self._parse_contracts_from_loaded_json(c, path) + @property def detectors(self): return self._detectors @@ -103,7 +142,10 @@ class Slither(SlitherSolc): :return: List of registered detectors results. """ - return [d.detect() for d in self._detectors] + self.load_previous_results() + results = [d.detect() for d in self._detectors] + self.write_results_to_hide() + return results def run_printers(self): """ @@ -173,3 +215,7 @@ class Slither(SlitherSolc): stdout = stdout.split('\n=') return stdout + + @property + def triage_mode(self): + return self._triage_mode