Make detectors and printers registrable

Thx to this PR Slither users will be able to plug-in their own detectors and printers.
pull/14/head
disconnect3d 6 years ago
parent cfb94e05b6
commit 16963d17b0
  1. 243
      slither/__main__.py
  2. 4
      slither/core/slitherCore.py
  3. 48
      slither/detectors/abstract_detector.py
  4. 11
      slither/detectors/attributes/constant_pragma.py
  5. 12
      slither/detectors/attributes/old_solc.py
  6. 5
      slither/detectors/detectorClassification.py
  7. 52
      slither/detectors/detectors.py
  8. 9
      slither/detectors/examples/backdoor.py
  9. 6
      slither/detectors/shadowing/shadowingFunctionsDetection.py
  10. 23
      slither/detectors/variables/uninitializedStateVarsDetection.py
  11. 14
      slither/printers/abstract_printer.py
  12. 2
      slither/printers/functions/authorization.py
  13. 51
      slither/printers/inheritance/printerInheritance.py
  14. 31
      slither/printers/printers.py
  15. 2
      slither/printers/summary/printerQuickSummary.py
  16. 2
      slither/printers/summary/printerSummary.py
  17. 84
      slither/slither.py
  18. 30
      slither/solcParsing/slitherSolc.py

@ -1,55 +1,51 @@
#!/usr/bin/env python3
import sys
import argparse
import logging
import traceback
import os
import glob
import json
import logging
import os
import sys
import traceback
from slither.slither import Slither
from slither.detectors.detectors import Detectors
from slither.printers.printers import Printers
logging.basicConfig()
logger = logging.getLogger("Slither")
def determineChecks(detectors, args):
if args.detectors_to_run:
return args.detectors_to_run
all_detectors = detectors.high + detectors.medium + detectors.low + detectors.code_quality
if args.exclude_informational:
all_detectors = [d for d in all_detectors if d not in detectors.code_quality]
if args.exclude_low:
all_detectors = [d for d in all_detectors if d not in detectors.low]
if args.exclude_medium:
all_detectors = [d for d in all_detectors if d not in detectors.medium]
if args.exclude_high:
all_detectors = [d for d in all_detectors if d not in detectors.high]
if args.detectors_to_exclude:
all_detectors = [d for d in all_detectors if d not in args.detectors_to_exclude]
return all_detectors
def process(filename, args, detectors, printers):
def process(filename, args, detector_classes, printer_classes):
"""
The core high-level code for running Slither static analysis.
Returns:
list(result), int: Result list and number of contracts analyzed
"""
slither = Slither(filename, args.solc, args.disable_solc_warnings, args.solc_args)
number_contract = len(slither.contracts)
if args.printers_to_run:
[printers.run_printer(slither, p) for p in args.printers_to_run]
return ([], number_contract)
else:
checks = determineChecks(detectors, args)
results = [detectors.run_detector(slither, c) for c in checks]
results = [x for x in results if x] # remove empty results
results = [item for sublist in results for item in sublist] #flatten
return (results, number_contract)
for detector_cls in detector_classes:
slither.register_detector(detector_cls)
for printer_cls in printer_classes:
slither.register_printer(printer_cls)
slither.analyze_contracts()
analyzed_contracts_count = len(slither.contracts)
results = []
if printer_classes:
slither.run_printers() # Currently printers does not return results
if detector_classes:
detector_results = slither.run_detectors()
detector_results = [x for x in detector_results if x] # remove empty results
detector_results = [item for sublist in detector_results for item in sublist] # flatten
results.extend(detector_results)
return results, analyzed_contracts_count
def output_json(results, filename):
@ -64,11 +60,88 @@ def exit(results):
def main():
detectors = Detectors()
printers = Printers()
"""
NOTE: This contains just a few detectors and printers that we made public.
"""
from slither.detectors.examples.backdoor import Backdoor
from slither.detectors.variables.uninitializedStateVarsDetection import UninitializedStateVarsDetection
from slither.detectors.attributes.constant_pragma import ConstantPragma
from slither.detectors.attributes.old_solc import OldSolc
detectors = [Backdoor, UninitializedStateVarsDetection, ConstantPragma, OldSolc]
from slither.printers.summary.printerSummary import PrinterSummary
from slither.printers.summary.printerQuickSummary import PrinterQuickSummary
from slither.printers.inheritance.printerInheritance import PrinterInheritance
from slither.printers.functions.authorization import PrinterWrittenVariablesAndAuthorization
printers = [PrinterSummary, PrinterQuickSummary, PrinterInheritance, PrinterWrittenVariablesAndAuthorization]
main_impl(all_detector_classes=detectors, all_printer_classes=printers)
def main_impl(all_detector_classes, all_printer_classes):
"""
: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.
"""
args = parse_args(all_detector_classes, all_printer_classes)
detector_classes = choose_detectors(args, all_detector_classes)
printer_classes = choose_printers(args, all_printer_classes)
default_log = logging.INFO if not args.debug else logging.DEBUG
for (l_name, l_level) in [('Slither', default_log),
('Contract', default_log),
('Function', default_log),
('Node', default_log),
('Parsing', default_log),
('Detectors', default_log),
('FunctionSolc', default_log),
('ExpressionParsing', default_log),
('TypeParsing', default_log),
('Printers', default_log)]:
l = logging.getLogger(l_name)
l.setLevel(l_level)
try:
filename = args.filename
if os.path.isfile(filename):
(results, number_contracts) = process(filename, args, detector_classes, printer_classes)
elif os.path.isdir(filename):
extension = "*.sol" if not args.solc_ast else "*.json"
filenames = glob.glob(os.path.join(filename, extension))
number_contracts = 0
results = []
for filename in filenames:
(results_tmp, number_contracts_tmp) = process(filename, args, detector_classes, printer_classes)
number_contracts += number_contracts_tmp
results += results_tmp
# if args.json:
# output_json(results, args.json)
# exit(results)
else:
raise Exception("Unrecognised file/dir path: '#{filename}'".format(filename=filename))
if args.json:
output_json(results, args.json)
logger.info('%s analyzed (%d contracts), %d result(s) found', filename, number_contracts, len(results))
exit(results)
except Exception:
logging.error('Error in %s' % args.filename)
logging.error(traceback.format_exc())
sys.exit(-1)
def parse_args(detector_classes, printer_classes):
parser = argparse.ArgumentParser(description='Slither',
usage="slither.py contract.sol [flag]", formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=35))
usage="slither.py contract.sol [flag]",
formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=35))
parser.add_argument('filename',
help='contract.sol file')
@ -118,87 +191,61 @@ def main():
action='store_true',
default=False)
for detector_name, Detector in detectors.detectors.items():
detector_arg = '--detect-{}'.format(Detector.ARGUMENT)
detector_help = 'Detection of {}'.format(Detector.HELP)
for detector_cls in detector_classes:
detector_arg = '--detect-{}'.format(detector_cls.ARGUMENT)
detector_help = 'Detection of {}'.format(detector_cls.HELP)
parser.add_argument(detector_arg,
help=detector_help,
action="append_const",
dest="detectors_to_run",
const=detector_name)
const=detector_cls.ARGUMENT)
for detector_name, Detector in detectors.detectors.items():
exclude_detector_arg = '--exclude-{}'.format(Detector.ARGUMENT)
exclude_detector_help = 'Exclude {} detector'.format(Detector.ARGUMENT)
exclude_detector_arg = '--exclude-{}'.format(detector_cls.ARGUMENT)
exclude_detector_help = 'Exclude {} detector'.format(detector_cls.ARGUMENT)
parser.add_argument(exclude_detector_arg,
help=exclude_detector_help,
action="append_const",
dest="detectors_to_exclude",
const=detector_name)
const=detector_cls.ARGUMENT)
for printer_name, Printer in printers.printers.items():
printer_arg = '--print-{}'.format(Printer.ARGUMENT)
printer_help = 'Print {}'.format(Printer.HELP)
for printer_cls in printer_classes:
printer_arg = '--print-{}'.format(printer_cls.ARGUMENT)
printer_help = 'Print {}'.format(printer_cls.HELP)
parser.add_argument(printer_arg,
help=printer_help,
action="append_const",
dest="printers_to_run",
const=printer_name)
const=printer_cls.ARGUMENT)
# Debug
parser.add_argument('--debug',
help='Debug mode',
action="store_true",
default=False)
args = parser.parse_args()
default_log = logging.INFO
if args.debug:
default_log = logging.DEBUG
for (l_name, l_level) in [('Slither', default_log),
('Contract', default_log),
('Function', default_log),
('Node', default_log),
('Parsing', default_log),
('Detectors', default_log),
('FunctionSolc', default_log),
('ExpressionParsing', default_log),
('TypeParsing', default_log),
('Printers', default_log)]:
l = logging.getLogger(l_name)
l.setLevel(l_level)
try:
filename = sys.argv[1]
if os.path.isfile(filename):
(results, number_contracts) = process(filename, args, detectors, printers)
elif os.path.isdir(filename):
extension = "*.sol" if not args.solc_ast else "*.json"
filenames = glob.glob(os.path.join(filename, extension))
number_contracts = 0
results = []
for filename in filenames:
(results_tmp, number_contracts_tmp) = process(filename, args, detectors, printers)
number_contracts += number_contracts_tmp
results += results_tmp
#if args.json:
# output_json(results, args.json)
#exit(results)
else:
raise Exception("Unrecognised file/dir path: '#{filename}'".format(filename=filename))
if args.json:
output_json(results, args.json)
logger.info('%s analyzed (%d contracts), %d result(s) found', filename, number_contracts, len(results))
exit(results)
except Exception as e:
logging.error('Error in %s'%sys.argv[1])
logging.error(traceback.format_exc())
sys.exit(-1)
return parser.parse_args()
def choose_detectors(args, all_detector_classes):
# TODO / FIXME: choose those =)
# if args.detectors_to_run:
# return args.detectors_to_run
# all_detectors = detectors.high + detectors.medium + detectors.low + detectors.code_quality
# if args.exclude_informational:
# all_detectors = [d for d in all_detectors if d not in detectors.code_quality]
# if args.exclude_low:
# all_detectors = [d for d in all_detectors if d not in detectors.low]
# if args.exclude_medium:
# all_detectors = [d for d in all_detectors if d not in detectors.medium]
# if args.exclude_high:
# all_detectors = [d for d in all_detectors if d not in detectors.high]
# if args.detectors_to_exclude:
# all_detectors = [d for d in all_detectors if d not in args.detectors_to_exclude]
return all_detector_classes
def choose_printers(args, all_printer_classes):
# TODO / FIXME: choose those =)
return all_printer_classes
if __name__ == '__main__':

@ -3,11 +3,11 @@
"""
import os
class Slither:
"""
Slither static analyzer
"""
name_class = 'Slither'
def __init__(self):
self._contracts = {}
@ -69,4 +69,4 @@ class Slither:
"""
for c in self.contracts:
for f in c.functions:
f.cfg_to_dot(os.path.join(d,'{}.{}.dot'.format(c.name, f.name)))
f.cfg_to_dot(os.path.join(d, '{}.{}.dot'.format(c.name, f.name)))

@ -1,30 +1,51 @@
import abc
import re
from slither.detectors.detectorClassification import DetectorClassification
from slither.utils.colors import green, yellow, red, blue
from slither.utils.colors import green, yellow, red
class IncorrectDetectorInitialization(Exception):
pass
class AbstractDetector(object, metaclass=abc.ABCMeta):
ARGUMENT = '' # run the detector with slither.py --ARGUMENT
HELP = '' # help information
class DetectorClassification:
LOW = 0
MEDIUM = 1
HIGH = 2
CODE_QUALITY = 3
classification_colors = {
DetectorClassification.CODE_QUALITY: green,
DetectorClassification.LOW: green,
DetectorClassification.MEDIUM: yellow,
DetectorClassification.HIGH: red,
}
class AbstractDetector(metaclass=abc.ABCMeta):
ARGUMENT = '' # run the detector with slither.py --ARGUMENT
HELP = '' # help information
CLASSIFICATION = None
HIDDEN_DETECTOR = False # yes if the detector should not be showed
HIDDEN_DETECTOR = False # yes if the detector should not be showed
def __init__(self, slither, logger):
self.slither = slither
self.contracts = slither.contracts
self.filename = slither.filename
self.logger = logger
if self.HELP == '':
if not self.HELP:
raise IncorrectDetectorInitialization('HELP is not initialized')
if self.ARGUMENT == '':
if not self.ARGUMENT:
raise IncorrectDetectorInitialization('ARGUMENT is not initialized')
if re.match('^[a-zA-Z0-9_-]*$', self.ARGUMENT) is None:
raise IncorrectDetectorInitialization('ARGUMENT has illegal character')
if not self.CLASSIFICATION in [DetectorClassification.LOW,
if self.CLASSIFICATION not in [DetectorClassification.LOW,
DetectorClassification.MEDIUM,
DetectorClassification.HIGH,
DetectorClassification.CODE_QUALITY]:
@ -41,11 +62,4 @@ class AbstractDetector(object, metaclass=abc.ABCMeta):
@property
def color(self):
if self.CLASSIFICATION == DetectorClassification.LOW:
return blue
if self.CLASSIFICATION == DetectorClassification.MEDIUM:
return yellow
if self.CLASSIFICATION == DetectorClassification.HIGH:
return red
if self.CLASSIFICATION == DetectorClassification.CODE_QUALITY:
return green
return classification_colors[self.CLASSIFICATION]

@ -2,8 +2,8 @@
Check that the same pragma is used in all the files
"""
from slither.detectors.abstractDetector import AbstractDetector
from slither.detectors.detectorClassification import DetectorClassification
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
class ConstantPragma(AbstractDetector):
"""
@ -14,19 +14,16 @@ class ConstantPragma(AbstractDetector):
HELP = 'different pragma directives'
CLASSIFICATION = DetectorClassification.CODE_QUALITY
def detect(self):
"""
"""
results = []
pragma = self.slither.pragma_directives
pragma = [''.join(p[1:]) for p in pragma]
pragma = list(set(pragma))
if len(pragma) > 1:
info = "Different version of Solidity used in {}: {}".format(self.filename, pragma)
self.log(info)
results.append({'vuln':'ConstantPragma', 'pragma': pragma})
results.append({'vuln': 'ConstantPragma', 'pragma': pragma})
return results

@ -3,8 +3,8 @@
Solidity >= 0.4.23 should be used
"""
from slither.detectors.abstractDetector import AbstractDetector
from slither.detectors.detectorClassification import DetectorClassification
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
class OldSolc(AbstractDetector):
"""
@ -15,15 +15,11 @@ class OldSolc(AbstractDetector):
HELP = 'an old version of Solidity used (<0.4.23)'
CLASSIFICATION = DetectorClassification.CODE_QUALITY
def detect(self):
"""
"""
results = []
pragma = self.slither.pragma_directives
pragma = [''.join(p[1:]) for p in pragma]
pragma = [p.replace('solidity','').replace('^','') for p in pragma]
pragma = [p.replace('solidity', '').replace('^', '') for p in pragma]
pragma = list(set(pragma))
old_pragma = [p for p in pragma if p not in ['0.4.23', '0.4.24']]
@ -31,6 +27,6 @@ class OldSolc(AbstractDetector):
info = "Old version of Solidity used in {}: {}".format(self.filename, old_pragma)
self.log(info)
results.append({'vuln':'OldPragma', 'pragma': old_pragma})
results.append({'vuln': 'OldPragma', 'pragma': old_pragma})
return results

@ -1,5 +0,0 @@
class DetectorClassification:
LOW = 0
MEDIUM = 1
HIGH = 2
CODE_QUALITY = 3

@ -1,52 +0,0 @@
import sys, inspect
import os
import logging
from slither.detectors.abstractDetector import AbstractDetector
from slither.detectors.detectorClassification import DetectorClassification
# Detectors must be imported here
from slither.detectors.examples.backdoor import Backdoor
from slither.detectors.variables.uninitializedStateVarsDetection import UninitializedStateVarsDetection
from slither.detectors.attributes.constant_pragma import ConstantPragma
from slither.detectors.attributes.old_solc import OldSolc
###
logger_detector = logging.getLogger("Detectors")
class Detectors:
def __init__(self):
self.detectors = {}
self.low = []
self.medium = []
self.high = []
self.code_quality = []
self._load_detectors()
def _load_detectors(self):
for name, obj in inspect.getmembers(sys.modules[__name__]):
if inspect.isclass(obj):
if issubclass(obj, AbstractDetector) and name != 'AbstractDetector':
if obj.HIDDEN_DETECTOR:
continue
if name in self.detectors:
raise Exception('Detector name collision: {}'.format(name))
self.detectors[name] = obj
if obj.CLASSIFICATION == DetectorClassification.LOW:
self.low.append(name)
elif obj.CLASSIFICATION == DetectorClassification.MEDIUM:
self.medium.append(name)
elif obj.CLASSIFICATION == DetectorClassification.HIGH:
self.high.append(name)
elif obj.CLASSIFICATION == DetectorClassification.CODE_QUALITY:
self.code_quality.append(name)
else:
raise Exception('Unknown classification')
def run_detector(self, slither, name):
Detector = self.detectors[name]
instance = Detector(slither, logger_detector)
return instance.detect()

@ -1,12 +1,12 @@
from slither.detectors.abstractDetector import AbstractDetector
from slither.detectors.detectorClassification import DetectorClassification
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
class Backdoor(AbstractDetector):
"""
Detect function named backdoor
"""
ARGUMENT = 'backdoor' # slither will launch the detector with slither.py --mydetector
ARGUMENT = 'backdoor' # slither will launch the detector with slither.py --mydetector
HELP = 'function named backdoor (detector example)'
CLASSIFICATION = DetectorClassification.HIGH
@ -21,5 +21,6 @@ class Backdoor(AbstractDetector):
# Print the info
self.log(info)
# Add the result in ret
ret.append({'vuln':'backdoor', 'contract':contract.name})
ret.append({'vuln': 'backdoor', 'contract': contract.name})
return ret

@ -3,8 +3,8 @@
It is more useful as summary printer than as vuln detection
"""
from slither.detectors.abstractDetector import AbstractDetector
from slither.detectors.detectorClassification import DetectorClassification
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
class ShadowingFunctionsDetection(AbstractDetector):
"""
@ -42,7 +42,7 @@ class ShadowingFunctionsDetection(AbstractDetector):
shadowing = self.detect_shadowing(c)
if shadowing:
for contract, funcs in shadowing.items():
results.append({'vuln':self.vuln_name,
results.append({'vuln': self.vuln_name,
'filename': self.filename,
'contractShadower': c.name,
'contract': contract.name,

@ -9,11 +9,11 @@
Only analyze "leaf" contracts (contracts that are not inherited by another contract)
"""
from slither.detectors.abstractDetector import AbstractDetector
from slither.detectors.detectorClassification import DetectorClassification
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.visitors.expression.findPush import FindPush
class UninitializedStateVarsDetection(AbstractDetector):
"""
Constant function detector
@ -40,14 +40,13 @@ class UninitializedStateVarsDetection(AbstractDetector):
# flat list
all_push = [item for sublist in all_push for item in sublist]
uninitialized_vars = list(set([v for v in var_read if\
v not in var_written and\
v not in all_push and\
str(v.type) not in contract.using_for])) # Note: does not handle using X for *
uninitialized_vars = list(set([v for v in var_read if \
v not in var_written and \
v not in all_push and \
str(v.type) not in contract.using_for])) # Note: does not handle using X for *
return [(v, contract.get_functions_reading_variable(v)) for v in uninitialized_vars]
def detect(self):
""" Detect uninitialized state variables
@ -59,13 +58,13 @@ class UninitializedStateVarsDetection(AbstractDetector):
for c in self.slither.contracts_derived:
ret = self.detect_uninitialized(c)
for variable, functions in ret:
info = "Uninitialized state variables in %s, "%self.filename +\
"Contract: %s, Vars: %s, Used in %s"%(c.name,
str(variable),
[str(f) for f in functions])
info = "Uninitialized state variables in %s, " % self.filename + \
"Contract: %s, Vars: %s, Used in %s" % (c.name,
str(variable),
[str(f) for f in functions])
self.log(info)
results.append({'vuln':'UninitializedStateVars',
results.append({'vuln': 'UninitializedStateVars',
'sourceMapping': c.source_mapping,
'filename': self.filename,
'contract': c.name,

@ -1,20 +1,24 @@
import abc
class IncorrectPrinterInitialization(Exception):
pass
class AbstractPrinter(object, metaclass=abc.ABCMeta):
ARGUMENT = '' # run the printer with slither.py --ARGUMENT
HELP = '' # help information
class AbstractPrinter(metaclass=abc.ABCMeta):
ARGUMENT = '' # run the printer with slither.py --ARGUMENT
HELP = '' # help information
def __init__(self, slither, logger):
self.slither = slither
self.contracts = slither.contracts
self.filename = slither.filename
self.logger = logger
if self.HELP == '':
if not self.HELP:
raise IncorrectPrinterInitialization('HELP is not initialized')
if self.ARGUMENT == '':
if not self.ARGUMENT:
raise IncorrectPrinterInitialization('ARGUMENT is not initialized')
def info(self, info):

@ -3,7 +3,7 @@
"""
from prettytable import PrettyTable
from slither.printers.abstractPrinter import AbstractPrinter
from slither.printers.abstract_printer import AbstractPrinter
from slither.core.declarations.function import Function
class PrinterWrittenVariablesAndAuthorization(AbstractPrinter):

@ -6,13 +6,12 @@
The output is a dot file named filename.dot
"""
from slither.printers.abstractPrinter import AbstractPrinter
from slither.core.declarations.contract import Contract
from slither.detectors.shadowing.shadowingFunctionsDetection import ShadowingFunctionsDetection
from slither.printers.abstract_printer import AbstractPrinter
from slither.core.declarations.contract import Contract
class PrinterInheritance(AbstractPrinter):
ARGUMENT = 'inheritance'
HELP = 'the inheritance graph'
@ -38,22 +37,22 @@ class PrinterInheritance(AbstractPrinter):
pattern_shadow = '<TR><TD align="left"><font color="#FFA500"> %s</font></TD></TR>'
if contract.name in self.functions_shadowed:
if func_name in self.functions_shadowed[contract.name]:
return pattern_shadow%func_name
return pattern%func_name
return pattern_shadow % func_name
return pattern % func_name
def _get_pattern_var(self, var, contract):
# Html pattern, each line is a row in a table
var_name = var.name
pattern = '<TR><TD align="left"> %s</TD></TR>'
pattern_contract = '<TR><TD align="left"> %s<font color="blue" POINT-SIZE="10"> (%s)</font></TD></TR>'
#pattern_arrow = '<TR><TD align="left" PORT="%s"><font color="blue"> %s</font></TD></TR>'
pattern_contract = '<TR><TD align="left"> %s<font color="blue" POINT-SIZE="10"> (%s)</font></TD></TR>'
# pattern_arrow = '<TR><TD align="left" PORT="%s"><font color="blue"> %s</font></TD></TR>'
if isinstance(var.type, Contract):
return pattern_contract%(var_name, str(var.type))
#return pattern_arrow%(self._get_port_id(var, contract), var_name)
return pattern%var_name
return pattern_contract % (var_name, str(var.type))
# return pattern_arrow%(self._get_port_id(var, contract), var_name)
return pattern % var_name
def _get_port_id(self, var, contract):
return "%s%s"%(var.name, contract.name)
return "%s%s" % (var.name, contract.name)
def _summary(self, contract):
"""
@ -62,43 +61,47 @@ class PrinterInheritance(AbstractPrinter):
ret = ''
# Add arrows
for i in contract.inheritances:
ret += '%s -> %s;\n'%(contract.name, i)
ret += '%s -> %s;\n' % (contract.name, i)
# Functions
visibilities = ['public', 'external']
public_functions = [self._get_pattern_func(f, contract) for f in contract.functions if not f.is_constructor and f.contract == contract and f.visibility in visibilities]
public_functions = [self._get_pattern_func(f, contract) for f in contract.functions if
not f.is_constructor and f.contract == contract and f.visibility in visibilities]
public_functions = ''.join(public_functions)
private_functions = [self._get_pattern_func(f, contract) for f in contract.functions if not f.is_constructor and f.contract == contract and f.visibility not in visibilities]
private_functions = [self._get_pattern_func(f, contract) for f in contract.functions if
not f.is_constructor and f.contract == contract and f.visibility not in visibilities]
private_functions = ''.join(private_functions)
# Modifiers
modifiers = [self._get_pattern_func(m, contract) for m in contract.modifiers if m.contract == contract]
modifiers = ''.join(modifiers)
# Public variables
public_variables = [self._get_pattern_var(v, contract) for v in contract.variables if v.visibility in visibilities]
public_variables = [self._get_pattern_var(v, contract) for v in contract.variables if
v.visibility in visibilities]
public_variables = ''.join(public_variables)
private_variables = [self._get_pattern_var(v, contract) for v in contract.variables if not v.visibility in visibilities]
private_variables = [self._get_pattern_var(v, contract) for v in contract.variables if
not v.visibility in visibilities]
private_variables = ''.join(private_variables)
# Build the node label
ret += '%s[shape="box"'%contract.name
ret += '%s[shape="box"' % contract.name
ret += 'label=< <TABLE border="0">'
ret += '<TR><TD align="center"><B>%s</B></TD></TR>'%contract.name
ret += '<TR><TD align="center"><B>%s</B></TD></TR>' % contract.name
if public_functions:
ret += '<TR><TD align="left"><I>Public Functions:</I></TD></TR>'
ret += '%s'%public_functions
ret += '%s' % public_functions
if private_functions:
ret += '<TR><TD align="left"><I>Private Functions:</I></TD></TR>'
ret += '%s'%private_functions
ret += '%s' % private_functions
if modifiers:
ret += '<TR><TD align="left"><I>Modifiers:</I></TD></TR>'
ret += '%s'%modifiers
ret += '%s' % modifiers
if public_variables:
ret += '<TR><TD align="left"><I>Public Variables:</I></TD></TR>'
ret += '%s'%public_variables
ret += '%s' % public_variables
if private_variables:
ret += '<TR><TD align="left"><I>Private Variables:</I></TD></TR>'
ret += '%s'%private_variables
ret += '%s' % private_variables
ret += '</TABLE> >];\n'
return ret
@ -111,7 +114,7 @@ class PrinterInheritance(AbstractPrinter):
"""
if not filename.endswith('.dot'):
filename += ".dot"
info = 'Inheritance Graph: '+filename
info = 'Inheritance Graph: ' + filename
self.info(info)
with open(filename, 'w') as f:
f.write('digraph{\n')

@ -1,31 +0,0 @@
import sys, inspect
import logging
from slither.printers.abstractPrinter import AbstractPrinter
# Printer must be imported here
from slither.printers.summary.printerSummary import PrinterSummary
from slither.printers.summary.printerQuickSummary import PrinterQuickSummary
from slither.printers.inheritance.printerInheritance import PrinterInheritance
from slither.printers.functions.authorization import PrinterWrittenVariablesAndAuthorization
logger_printer = logging.getLogger("Printers")
class Printers:
def __init__(self):
self.printers = {}
self._load_printers()
def _load_printers(self):
for name, obj in inspect.getmembers(sys.modules[__name__]):
if inspect.isclass(obj):
if issubclass(obj, AbstractPrinter) and name != 'AbstractPrinter':
if name in self.printers:
raise Exception('Printer name collision: {}'.format(name))
self.printers[name] = obj
def run_printer(self, slither, name):
Printer = self.printers[name]
instance = Printer(slither, logger_printer)
return instance.output(slither.filename)

@ -2,7 +2,7 @@
Module printing summary of the contract
"""
from slither.printers.abstractPrinter import AbstractPrinter
from slither.printers.abstract_printer import AbstractPrinter
from slither.utils.colors import blue, green, magenta
class PrinterQuickSummary(AbstractPrinter):

@ -3,7 +3,7 @@
"""
from prettytable import PrettyTable
from slither.printers.abstractPrinter import AbstractPrinter
from slither.printers.abstract_printer import AbstractPrinter
class PrinterSummary(AbstractPrinter):

@ -1,19 +1,86 @@
import os
import sys
import logging
import os
import subprocess
import sys
from slither.detectors.abstract_detector import AbstractDetector
from slither.printers.abstract_printer import AbstractPrinter
from .solcParsing.slitherSolc import SlitherSolc
from .utils.colors import red
logger = logging.getLogger("Slither")
logging.basicConfig()
logger_detector = logging.getLogger("Detectors")
logger_printer = logging.getLogger("Printers")
class Slither(SlitherSolc):
def __init__(self, filename, solc='solc', disable_solc_warnings=False ,solc_arguments=''):
def __init__(self, filename, solc='solc', disable_solc_warnings=False, solc_arguments=''):
self._detectors = []
self._printers = []
stdout = self._run_solc(filename, solc, disable_solc_warnings, solc_arguments)
super(Slither, self).__init__(filename)
for d in stdout:
self.parse_contracts_from_json(d)
def register_detector(self, detector_class):
"""
:param detector_class: Class inheriting from `AbstractDetector`.
"""
self._check_common_things('detector', detector_class, AbstractDetector, self._detectors)
instance = detector_class(self, logger_detector)
self._detectors.append(instance)
def register_printer(self, printer_class):
"""
:param printer_class: Class inheriting from `AbstractPrinter`.
"""
self._check_common_things('printer', printer_class, AbstractPrinter, self._printers)
instance = printer_class(self, logger_printer)
self._printers.append(instance)
def run_detectors(self):
"""
:return: List of registered detectors results.
"""
if not self.analyzed:
raise Exception('Launch analysis first by calling {}!'.format(self.analyze_contracts.__name__))
return [d.detect() for d in self._detectors]
def run_printers(self):
"""
:return: List of registered printers outputs.
"""
if not self.analyzed:
raise Exception('Launch analysis first by calling {}!'.format(self.analyze_contracts.__name__))
return [p.output(self.filename) for p in self._printers]
def _check_common_things(self, thing_name, cls, base_cls, instances_list):
if self.analyzed:
raise Exception('There is no point of registering {} after running the analyzis.'.format(thing_name))
if not issubclass(cls, base_cls) or cls is base_cls:
raise Exception(
"You can't register {!r} as a {}. You need to pass a class that inherits from {}".format(
cls, thing_name, base_cls.__name__
)
)
if any(isinstance(obj, cls) for obj in instances_list):
raise Exception(
"You can't register {!r} twice.".format(cls)
)
def _run_solc(self, filename, solc, disable_solc_warnings, solc_arguments):
if not os.path.isfile(filename):
logger.error('{} does not exist (are you in the correct directory?)'.format(filename))
exit(-1)
@ -38,7 +105,7 @@ class Slither(SlitherSolc):
# One solc option may have multiple argument sepparated with ' '
# For example: --allow-paths /tmp .
# split() removes the delimiter, so we add it again
solc_args = [('--'+x).split(' ', 1) for x in solc_args if x]
solc_args = [('--' + x).split(' ', 1) for x in solc_args if x]
# Flat the list of list
solc_args = [item for sublist in solc_args for item in sublist]
cmd += solc_args
@ -59,11 +126,4 @@ class Slither(SlitherSolc):
stdout = stdout.split('\n=')
super(Slither, self).__init__(filename)
for d in stdout:
self.parse_contracts_from_json(d)
self.analyze_contracts()
return stdout

@ -7,6 +7,7 @@ logger = logging.getLogger("SlitherSolcParsing")
from slither.solcParsing.declarations.contractSolc04 import ContractSolc04
from slither.core.slitherCore import Slither
class SlitherSolc(Slither):
def __init__(self, filename):
@ -14,11 +15,12 @@ class SlitherSolc(Slither):
self._filename = filename
self._contractsNotParsed = []
self._contracts_by_id = {}
self._analyzed = False
def parse_contracts_from_json(self, json_data):
first = json_data.find('{')
if first != -1:
last = json_data.rfind('}') +1
last = json_data.rfind('}') + 1
filename = json_data[0:first]
json_data = json_data[first:last]
@ -26,19 +28,19 @@ class SlitherSolc(Slither):
if data_loaded['name'] == 'root':
self._solc_version = '0.3'
logger.error('solc <0.4 not supported')
logger.error('solc <0.4 is not supported')
exit(-1)
elif data_loaded['name'] == 'SourceUnit':
self._solc_version = '0.4'
self._parse_source_unit(data_loaded, filename)
else:
logger.error('solc version not supported')
logger.error('solc version is not supported')
exit(-1)
for contract_data in data_loaded['children']:
# if self.solc_version == '0.3':
# assert contract_data['name'] == 'Contract'
# contract = ContractSolc03(self, contract_data)
# if self.solc_version == '0.3':
# assert contract_data['name'] == 'Contract'
# contract = ContractSolc03(self, contract_data)
if self.solc_version == '0.4':
assert contract_data['name'] in ['ContractDefinition', 'PragmaDirective', 'ImportDirective']
if contract_data['name'] == 'ContractDefinition':
@ -54,7 +56,7 @@ class SlitherSolc(Slither):
def _parse_source_unit(self, data, filename):
if data['name'] != 'SourceUnit':
return -1 # handle solc prior 0.3.6
return -1 # handle solc prior 0.3.6
# match any char for filename
# filename can contain space, /, -, ..
@ -62,7 +64,7 @@ class SlitherSolc(Slither):
assert len(name) == 1
name = name[0]
sourceUnit = -1 # handle old solc, or error
sourceUnit = -1 # handle old solc, or error
if 'src' in data:
sourceUnit = re.findall('[0-9]*:[0-9]*:([0-9]*)', data['src'])
if len(sourceUnit) == 1:
@ -71,6 +73,8 @@ class SlitherSolc(Slither):
self._source_units[sourceUnit] = name
def analyze_contracts(self):
if self._analyzed:
raise Exception('Contract analysis can be run only once!')
# First we save all the contracts in a dict
# the key is the contractid
@ -105,9 +109,14 @@ class SlitherSolc(Slither):
# Then we analyse state variables, functions and modifiers
self._analyze_third_part(contracts_to_be_analyzed, libraries)
self._analyzed = True
# TODO refactor the following functions, and use a lambda function
@property
def analyzed(self):
return self._analyzed
def _analyze_all_enums(self, contracts_to_be_analyzed):
while contracts_to_be_analyzed:
contract = contracts_to_be_analyzed[0]
@ -121,7 +130,6 @@ class SlitherSolc(Slither):
contracts_to_be_analyzed += [contract]
return
def _analyze_first_part(self, contracts_to_be_analyzed, libraries):
for lib in libraries:
self._parse_struct_var_modifiers_functions(lib)
@ -143,7 +151,6 @@ class SlitherSolc(Slither):
contracts_to_be_analyzed += [contract]
return
def _analyze_second_part(self, contracts_to_be_analyzed, libraries):
for lib in libraries:
self._analyze_struct_events(lib)
@ -192,7 +199,7 @@ class SlitherSolc(Slither):
contract.set_is_analyzed(True)
def _parse_struct_var_modifiers_functions(self, contract):
contract.parse_structs() # struct can refer another struct
contract.parse_structs() # struct can refer another struct
contract.parse_state_variables()
contract.parse_modifiers()
contract.parse_functions()
@ -220,4 +227,3 @@ class SlitherSolc(Slither):
contract.analyze_content_functions()
contract.set_is_analyzed(True)

Loading…
Cancel
Save