diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c5ec555ce..0a0f04f2b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -47,7 +47,7 @@ jobs: uses: pypa/gh-action-pypi-publish@v1.9.0 - name: sign - uses: sigstore/gh-action-sigstore-python@v2.1.1 + uses: sigstore/gh-action-sigstore-python@v3.0.0 with: inputs: ./dist/*.tar.gz ./dist/*.whl release-signing-artifacts: true diff --git a/setup.py b/setup.py index 4a7e44dca..344153bbd 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ python_requires=">=3.8", install_requires=[ "packaging", - "prettytable>=3.3.0", + "prettytable>=3.10.2", "pycryptodome>=3.4.6", "crytic-compile>=0.3.7,<0.4.0", # "crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile", diff --git a/slither/printers/summary/loc.py b/slither/printers/summary/loc.py index 35bb20fc4..886803e8e 100644 --- a/slither/printers/summary/loc.py +++ b/slither/printers/summary/loc.py @@ -17,8 +17,8 @@ class LocPrinter(AbstractPrinter): ARGUMENT = "loc" - HELP = """Count the total number lines of code (LOC), source lines of code (SLOC), \ - and comment lines of code (CLOC) found in source files (SRC), dependencies (DEP), \ + HELP = """Count the total number lines of code (LOC), source lines of code (SLOC), + and comment lines of code (CLOC) found in source files (SRC), dependencies (DEP), and test files (TEST).""" WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#loc" diff --git a/slither/tools/mutator/mutators/AOR.py b/slither/tools/mutator/mutators/AOR.py index 0bf0fb2a2..f86e7d3d4 100644 --- a/slither/tools/mutator/mutators/AOR.py +++ b/slither/tools/mutator/mutators/AOR.py @@ -2,7 +2,12 @@ from slither.slithir.operations import Binary, BinaryType from slither.tools.mutator.utils.patch import create_patch_with_line from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator +from slither.core.variables.variable import Variable from slither.core.expressions.unary_operation import UnaryOperation +from slither.core.expressions.call_expression import CallExpression +from slither.core.expressions.member_access import MemberAccess +from slither.core.expressions.identifier import Identifier +from slither.core.solidity_types.array_type import ArrayType arithmetic_operators = [ BinaryType.ADDITION, @@ -27,7 +32,39 @@ def _mutate(self) -> Dict: ir_expression = node.expression except: # pylint: disable=bare-except continue - for ir in node.irs: + + # Special cases handling .push and .pop on dynamic arrays. + # The IR for these operations has a binary operation due to internal conversion + # (see convert_to_push and convert_to_pop in slithir/convert.py) + # however it's not present in the source code and should not be mutated. + # pylint: disable=too-many-boolean-expressions + if ( + isinstance(ir_expression, CallExpression) + and isinstance(ir_expression.called, MemberAccess) + and ir_expression.called.member_name == "pop" + and isinstance(ir_expression.called.expression, Identifier) + and isinstance(ir_expression.called.expression.value, Variable) + and isinstance(ir_expression.called.expression.value.type, ArrayType) + ): + continue + + # For a .push instruction we skip the last 6 IR operations + # because they are fixed based on the internal conversion to the IR + # while we need to look at the preceding instructions because + # they might contain Binary IR to be mutated. + # For example for a.push(3+x) it's correct to mutate 3+x. + irs = ( + node.irs[:-6] + if isinstance(ir_expression, CallExpression) + and isinstance(ir_expression.called, MemberAccess) + and ir_expression.called.member_name == "push" + and isinstance(ir_expression.called.expression, Identifier) + and isinstance(ir_expression.called.expression.value, Variable) + and isinstance(ir_expression.called.expression.value.type, ArrayType) + else node.irs + ) + + for ir in irs: if isinstance(ir, Binary) and ir.type in arithmetic_operators: if isinstance(ir_expression, UnaryOperation): continue diff --git a/slither/utils/command_line.py b/slither/utils/command_line.py index f5b9ab452..b8888cb85 100644 --- a/slither/utils/command_line.py +++ b/slither/utils/command_line.py @@ -360,8 +360,10 @@ def output_printers(printer_classes: List[Type[AbstractPrinter]]) -> None: printers_list = sorted(printers_list, key=lambda element: (element[0])) idx = 1 for (argument, help_info) in printers_list: - table.add_row([str(idx), argument, help_info]) + # Clean multi line HELP info + table.add_row([str(idx), argument, " ".join(x.strip() for x in help_info.splitlines())]) idx = idx + 1 + print(table) diff --git a/slither/utils/myprettytable.py b/slither/utils/myprettytable.py index b33fb9c5f..ac666a501 100644 --- a/slither/utils/myprettytable.py +++ b/slither/utils/myprettytable.py @@ -1,3 +1,4 @@ +from shutil import get_terminal_size from typing import List, Dict, Union from prettytable import PrettyTable @@ -7,7 +8,12 @@ class MyPrettyTable: - def __init__(self, field_names: List[str], pretty_align: bool = True): # TODO: True by default? + def __init__( + self, + field_names: List[str], + pretty_align: bool = True, + max_width: Union[int, None] = "max", # Default value is "max" + ): self._field_names = field_names self._rows: List = [] self._options: Dict = {} @@ -19,6 +25,17 @@ def __init__(self, field_names: List[str], pretty_align: bool = True): # TODO: else: self._options["set_alignment"] = [] + self.max_width = None + if max_width == "max": + # We use (0,0) as a fallback to detect if we are not attached to a terminal + # In this case, we fall back to the default behavior (i.e. printing as much as possible) + terminal_column = get_terminal_size((0, 0)).columns + if terminal_column != 0: + # We reduce slightly the max-width to take into account inconsistencies in terminals + self.max_width = terminal_column - 3 + else: + self.max_width = max_width + def add_row(self, row: List[Union[str, List[str]]]) -> None: self._rows.append(row) @@ -28,6 +45,9 @@ def to_pretty_table(self) -> PrettyTable: else: table = PrettyTable(self._field_names) + if self.max_width is not None: + table.max_table_width = self.max_width + for row in self._rows: table.add_row(row) if len(self._options["set_alignment"]): @@ -63,7 +83,5 @@ def make_pretty_table( table_row = [row] + [body[row][key] for key in headers[1:]] table.add_row(table_row) if totals: - table.add_row( - [total_header] + [sum([body[row][key] for row in body]) for key in headers[1:]] - ) + table.add_row([total_header] + [sum(body[row][key] for row in body) for key in headers[1:]]) return table