cli improvements

- help is printed when no arguments are supplied
- rework detector arguments: detectors are now a comma-separated list, defaults to all
- rework printer arguments: printers are now a comma-separated list, defaults to contract-summary
- add version command
- add --list-detectors and --list-printers
- update README
pull/55/head
redshark1802 6 years ago
parent 68f32a980c
commit f6ed3ba68d
  1. 51
      README.md
  2. 30
      scripts/travis_test.sh
  3. 167
      slither/__main__.py

@ -36,32 +36,39 @@ If Slither is run on a directory, it will run on every `.sol` file of the direct
### Printers
* `--printer-summary`: Print a summary of the contracts
* `--printer-quick-summary`: Print a quick summary of the contracts
* `--printer-inheritance`: Print the inheritance relations
* `--printer-inheritance-graph`: Print the inheritance graph in a file
* `--printer-vars-and-auth`: Print the variables written and the check on `msg.sender` of each function
By default, the `contract-summary` printer is used. Use --printers comma-separated list of printers,
or `none` to disable the default printer.
## Checks available
Num | Printer | Description
--- | --- | ---
1 | `contract-summary` | a summary of the contract
2 | `function-summary` | the summary of the functions
3 | `inheritance` | the inheritance relation between contracts
4 | `inheritance-graph` | the inheritance graph
5 | `slithir` | the slithIR
6 | `vars-and-auth` | the state variables written and the authorization of the functions
By default, all the checks are run. Use --detect-_name-of-check_ to run one check at a time.
## Detectors
Num | Check | What it Detects | Impact | Confidence
By default, all the detectors are run. Use --detectors comma-separated list of detectors to run.
Num | Detector | What it Detects | Impact | Confidence
--- | --- | --- | --- | ---
1 | `suicidal` | Suicidal functions | High | High
2 | `uninitialized-state` | Uninitialized state variables | High | High
3 | `uninitialized-storage` | Uninitialized storage variables | High | High
4 | `arbitrary-send` | Functions that send ether to an arbitrary destination | High | Medium
5 | `reentrancy` | Reentrancy vulnerabilities | High | Medium
6 | `locked-ether` | Contracts that lock ether | Medium | High
7 | `tx-origin` | Dangerous usage of `tx.origin` | Medium | Medium
8 | `assembly` | Assembly usage | Informational | High
9 | `const-candidates-state` | State variables that could be declared constant | Informational | High
10 | `low-level-calls` | Low level calls | Informational | High
11 | `naming-convention` | Conformance to Solidity naming conventions | Informational | High
12 | `pragma` | If different pragma directives are used | Informational | High
13 | `solc-version` | If an old version of Solidity used (<0.4.23) | Informational | High
14 | `unused-state` | Unused state variables | Informational | High
1 | `backdoor` | Function named backdoor (detector example) | High | High
2 | `suicidal` | Suicidal functions | High | High
3 | `uninitialized-state` | Uninitialized state variables | High | High
4 | `uninitialized-storage` | Uninitialized storage variables | High | High
5 | `arbitrary-send` | Functions that send ether to an arbitrary destination | High | Medium
6 | `reentrancy` | Reentrancy vulnerabilities | High | Medium
7 | `locked-ether` | Contracts that lock ether | Medium | High
8 | `tx-origin` | Dangerous usage of `tx.origin` | Medium | Medium
9 | `assembly` | Assembly usage | Informational | High
10 | `const-candidates-state` | State variables that could be declared constant | Informational | High
11 | `low-level-calls` | Low level calls | Informational | High
12 | `naming-convention` | Conformance to Solidity naming conventions | Informational | High
13 | `pragma` | If different pragma directives are used | Informational | High
14 | `solc-version` | If an old version of Solidity used (<0.4.23) | Informational | High
15 | `unused-state` | Unused state variables | Informational | High
[Contact us](https://www.trailofbits.com/contact/) to get access to additional detectors.

@ -3,79 +3,79 @@
### Test Detectors
slither tests/uninitialized.sol --disable-solc-warnings --detect-uninitialized-state
slither tests/uninitialized.sol --disable-solc-warnings --detectors uninitialized-state
if [ $? -ne 1 ]; then
exit 1
fi
# contains also the test for the suicidal detector
slither tests/backdoor.sol --disable-solc-warnings --detect-backdoor
slither tests/backdoor.sol --disable-solc-warnings --detectors backdoor
if [ $? -ne 1 ]; then
exit 1
fi
slither tests/pragma.0.4.24.sol --disable-solc-warnings --detect-pragma
slither tests/pragma.0.4.24.sol --disable-solc-warnings --detectors pragma
if [ $? -ne 1 ]; then
exit 1
fi
slither tests/old_solc.sol.json --solc-ast --detect-solc-version
slither tests/old_solc.sol.json --solc-ast --detectors solc-version
if [ $? -ne 1 ]; then
exit 1
fi
slither tests/reentrancy.sol --disable-solc-warnings --detect-reentrancy
slither tests/reentrancy.sol --disable-solc-warnings --detectors reentrancy
if [ $? -ne 1 ]; then
exit 1
fi
slither tests/uninitialized_storage_pointer.sol --disable-solc-warnings --detect-uninitialized-storage
slither tests/uninitialized_storage_pointer.sol --disable-solc-warnings --detectors uninitialized-storage
if [ $? -ne 1 ]; then
exit 1
fi
slither tests/tx_origin.sol --disable-solc-warnings --detect-tx-origin
slither tests/tx_origin.sol --disable-solc-warnings --detectors tx-origin
if [ $? -ne 2 ]; then
exit 1
fi
slither tests/unused_state.sol --detect-unused-state
slither tests/unused_state.sol --detectors unused-state
if [ $? -ne 1 ]; then
exit 1
fi
slither tests/locked_ether.sol --detect-locked-ether
slither tests/locked_ether.sol --detectors locked-ether
if [ $? -ne 1 ]; then
exit 1
fi
slither tests/arbitrary_send.sol --disable-solc-warnings --detect-arbitrary-send
slither tests/arbitrary_send.sol --disable-solc-warnings --detectors arbitrary-send
if [ $? -ne 2 ]; then
exit 1
fi
slither tests/inline_assembly_contract.sol --disable-solc-warnings --detect-assembly
slither tests/inline_assembly_contract.sol --disable-solc-warnings --detectors assembly
if [ $? -ne 1 ]; then
exit 1
fi
slither tests/inline_assembly_library.sol --disable-solc-warnings --detect-assembly
slither tests/inline_assembly_library.sol --disable-solc-warnings --detectors assembly
if [ $? -ne 2 ]; then
exit 1
fi
slither tests/naming_convention.sol --disable-solc-warnings --detect-naming-convention
slither tests/naming_convention.sol --disable-solc-warnings --detectors naming-convention
if [ $? -ne 10 ]; then
exit 1
fi
slither tests/low_level_calls.sol --disable-solc-warnings --detect-low-level-calls
slither tests/low_level_calls.sol --disable-solc-warnings --detectors low-level-calls
if [ $? -ne 1 ]; then
exit 1
fi
slither tests/const_state_variables.sol --detect-const-candidates-state
slither tests/const_state_variables.sol --detectors const-candidates-state
if [ $? -ne 2 ]; then
exit 1
fi

@ -8,7 +8,7 @@ import os
import sys
import traceback
from pkg_resources import iter_entry_points
from pkg_resources import iter_entry_points, require
from slither.detectors.abstract_detector import (AbstractDetector,
DetectorClassification,
@ -19,7 +19,8 @@ from slither.slither import Slither
logging.basicConfig()
logger = logging.getLogger("Slither")
def output_to_markdown(detector_classes):
def output_detectors(detector_classes):
"""
Pretty print of the detectors to README.md
"""
@ -27,8 +28,6 @@ def output_to_markdown(detector_classes):
for detector in detector_classes:
argument = detector.ARGUMENT
# dont show the backdoor example
if argument == 'backdoor':
continue
help_info = detector.HELP
impact = detector.IMPACT
confidence = classification_txt[detector.CONFIDENCE]
@ -43,7 +42,27 @@ def output_to_markdown(detector_classes):
help_info,
classification_txt[impact],
confidence))
idx = idx +1
idx = idx + 1
def output_printers(printer_classes):
"""
Pretty print of the printers to README.md
"""
printers_list = []
for printer in printer_classes:
argument = printer.ARGUMENT
help_info = printer.HELP
printers_list.append((argument, help_info))
print(printers_list)
# Sort by name
printers_list = sorted(printers_list, key=lambda element: (element[0]))
idx = 1
for (argument, help_info) in printers_list:
print('{} | `{}` | {} '.format(idx, argument, help_info))
idx = idx + 1
def process(filename, args, detector_classes, printer_classes):
"""
@ -64,15 +83,13 @@ def process(filename, args, detector_classes, printer_classes):
results = []
if printer_classes:
slither.run_printers() # Currently printers does not return results
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
elif 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)
results.extend(detector_results)
slither.run_printers() # Currently printers does not return results
return results, analyzed_contracts_count
@ -164,8 +181,12 @@ def main_impl(all_detector_classes, all_printer_classes):
"""
args = parse_args(all_detector_classes, all_printer_classes)
if args.markdown:
output_to_markdown(all_detector_classes)
if args.list_detectors:
output_detectors(all_detector_classes)
return
if args.list_printers:
output_printers(all_printer_classes)
return
detector_classes = choose_detectors(args, all_detector_classes)
@ -205,9 +226,6 @@ def main_impl(all_detector_classes, all_printer_classes):
(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))
@ -229,17 +247,48 @@ def main_impl(all_detector_classes, all_printer_classes):
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]")
parser.add_argument('filename',
help='contract.sol file')
parser.add_argument('--version',
help='displays the current version',
version=require('slither-analyzer')[0].version,
action='version')
parser.add_argument('--solc',
help='solc path',
action='store',
default='solc')
parser.add_argument('--detectors',
help='Comma-separated list of detectors, defaults to all, '
'available detectors: {}'.format(', '.join(d.ARGUMENT for d in detector_classes)),
action='store',
dest='detectors_to_run',
default='all')
parser.add_argument('--printers',
help='Comma-separated list fo contract information printers, '
'defaults to contract-summary and can be disabled by using \'none\', '
'available printers: {}'.format(', '.join(d.ARGUMENT for d in printer_classes)),
action='store',
dest='printers_to_run',
default='contract-summary')
parser.add_argument('--output',
help='Define output encoding',
action='store',
choices=['stdout', 'json'],
default='stdout')
parser.add_argument('--exclude-detectors',
help='Comma-separated list of detectors that should be excluded',
action='store',
dest='detectors_to_exclude',
default='')
parser.add_argument('--solc-args',
help='Add custom solc arguments. Example: --solc-args "--allow-path /tmp --evm-version byzantium".',
action='store',
@ -280,34 +329,6 @@ def parse_args(detector_classes, printer_classes):
action='store_true',
default=False)
for detector_cls in detector_classes:
detector_arg = '--detect-{}'.format(detector_cls.ARGUMENT)
detector_help = '{}'.format(detector_cls.HELP)
parser.add_argument(detector_arg,
help=detector_help,
action="append_const",
dest="detectors_to_run",
const=detector_cls.ARGUMENT)
# Second loop so that the --exclude are shown after all the detectors
for detector_cls in detector_classes:
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_cls.ARGUMENT)
for printer_cls in printer_classes:
printer_arg = '--printer-{}'.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_cls.ARGUMENT)
# debugger command
parser.add_argument('--debug',
help=argparse.SUPPRESS,
@ -319,15 +340,44 @@ def parse_args(detector_classes, printer_classes):
action="store_true",
default=False)
return parser.parse_args()
parser.add_argument('--list-detectors',
help='List available detectors',
action='store_true',
default=False)
parser.add_argument('--list-printers',
help='List available printers',
action='store_true',
default=False)
if len(sys.argv) == 1:
parser.print_help(sys.stderr)
sys.exit(1)
args = parser.parse_args()
return args
def choose_detectors(args, all_detector_classes):
# If detectors are specified, run only these ones
if args.detectors_to_run:
return [d for d in all_detector_classes if d.ARGUMENT in args.detectors_to_run]
detectors_to_run = all_detector_classes
detectors_to_run = []
detectors = {d.ARGUMENT: d for d in 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])
else:
for d in args.detectors_to_run.split(','):
if d in detectors:
detectors_to_run.append(detectors[d])
else:
raise Exception('Error: {} is not a detector'.format(d))
return detectors_to_run
if args.exclude_informational:
detectors_to_run = [d for d in detectors_to_run if
@ -348,11 +398,18 @@ def choose_detectors(args, all_detector_classes):
def choose_printers(args, all_printer_classes):
# by default, dont run any printer
printers_to_run = []
if args.printers_to_run:
printers_to_run = [p for p in all_printer_classes if
p.ARGUMENT in args.printers_to_run]
# disable default printer
if args.printers_to_run == 'none':
return printers_to_run
printers = {p.ARGUMENT: p for p in all_printer_classes}
for p in args.printers_to_run.split(','):
if p in printers:
printers_to_run.append(printers[p])
else:
raise Exception('Error: {} is not a printer'.format(p))
return printers_to_run

Loading…
Cancel
Save