Skip to content

Commit

Permalink
Merge branch 'dev' into issue-1654
Browse files Browse the repository at this point in the history
  • Loading branch information
montyly authored Oct 13, 2023
2 parents 00bd1d4 + 2c76c94 commit 2d2c77a
Show file tree
Hide file tree
Showing 728 changed files with 670 additions and 3,962 deletions.
19 changes: 19 additions & 0 deletions slither/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ def process_single(
ast = "--ast-json"
slither = Slither(target, ast_format=ast, **vars(args))

if args.sarif_input:
slither.sarif_input = args.sarif_input
if args.sarif_triage:
slither.sarif_triage = args.sarif_triage

return _process(slither, detector_classes, printer_classes)


Expand Down Expand Up @@ -469,6 +474,20 @@ def parse_args(
default=defaults_flag_in_config["sarif"],
)

group_misc.add_argument(
"--sarif-input",
help="Sarif input (beta)",
action="store",
default=defaults_flag_in_config["sarif_input"],
)

group_misc.add_argument(
"--sarif-triage",
help="Sarif triage (beta)",
action="store",
default=defaults_flag_in_config["sarif_triage"],
)

group_misc.add_argument(
"--json-types",
help="Comma-separated list of result types to output to JSON, defaults to "
Expand Down
10 changes: 10 additions & 0 deletions slither/core/cfg/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class NodeType(Enum):
IF = "IF"
VARIABLE = "NEW VARIABLE" # Variable declaration
ASSEMBLY = "INLINE ASM"
ENDASSEMBLY = "END INLINE ASM"
IFLOOP = "IF_LOOP"

# Nodes where control flow merges
Expand Down Expand Up @@ -193,6 +194,8 @@ def __init__(
self.file_scope: "FileScope" = file_scope
self._function: Optional["Function"] = None

self._is_reachable: bool = False

###################################################################################
###################################################################################
# region General's properties
Expand Down Expand Up @@ -234,6 +237,13 @@ def set_function(self, function: "Function") -> None:
def function(self) -> "Function":
return self._function

@property
def is_reachable(self) -> bool:
return self._is_reachable

def set_is_reachable(self, new_is_reachable: bool) -> None:
self._is_reachable = new_is_reachable

# endregion
###################################################################################
###################################################################################
Expand Down
1 change: 1 addition & 0 deletions slither/core/declarations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
from .custom_error_contract import CustomErrorContract
from .custom_error_top_level import CustomErrorTopLevel
from .custom_error import CustomError
from .solidity_import_placeholder import SolidityImportPlaceHolder
8 changes: 8 additions & 0 deletions slither/core/declarations/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope
# do not contain private variables inherited from contract
self._variables: Dict[str, "StateVariable"] = {}
self._variables_ordered: List["StateVariable"] = []
# Reference id -> variable declaration (only available for compact AST)
self._state_variables_by_ref_id: Dict[int, "StateVariable"] = {}
self._modifiers: Dict[str, "Modifier"] = {}
self._functions: Dict[str, "FunctionContract"] = {}
self._linearizedBaseContracts: List[int] = []
Expand Down Expand Up @@ -404,6 +406,12 @@ def type_aliases_as_dict(self) -> Dict[str, "TypeAliasContract"]:
# region Variables
###################################################################################
###################################################################################
@property
def state_variables_by_ref_id(self) -> Dict[int, "StateVariable"]:
"""
Returns the state variables by reference id (only available for compact AST).
"""
return self._state_variables_by_ref_id

@property
def variables(self) -> List["StateVariable"]:
Expand Down
15 changes: 15 additions & 0 deletions slither/core/dominators/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,21 @@
def intersection_predecessor(node: "Node") -> Set["Node"]:
if not node.fathers:
return set()

# Revert PR1984
ret = node.fathers[0].dominators
for pred in node.fathers[1:]:
ret = ret.intersection(pred.dominators)
# if not any(father.is_reachable for father in node.fathers):
# return set()
#
# ret = set()
# for pred in node.fathers:
# ret = ret.union(pred.dominators)
#
# for pred in node.fathers:
# if pred.is_reachable:
# ret = ret.intersection(pred.dominators)
return ret


Expand Down Expand Up @@ -84,6 +96,9 @@ def compute_dominance_frontier(nodes: List["Node"]) -> None:
for node in nodes:
if len(node.fathers) >= 2:
for father in node.fathers:
# Revert PR1984
# if not father.is_reachable:
# continue
runner = father
# Corner case: if there is a if without else
# we need to add update the conditional node
Expand Down
22 changes: 22 additions & 0 deletions slither/core/slither_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from slither.core.source_mapping.source_mapping import SourceMapping, Source
from slither.slithir.variables import Constant
from slither.utils.colors import red
from slither.utils.sarif import read_triage_info
from slither.utils.source_mapping import get_definition, get_references, get_implementation

logger = logging.getLogger("Slither")
Expand Down Expand Up @@ -48,6 +49,10 @@ def __init__(self) -> None:
self._source_code_to_line: Optional[Dict[str, List[str]]] = None

self._previous_results_filename: str = "slither.db.json"

# TODO: add cli flag to set these variables
self.sarif_input: str = "export.sarif"
self.sarif_triage: str = "export.sarif.sarifexplorer"
self._results_to_hide: List = []
self._previous_results: List = []
# From triaged result
Expand Down Expand Up @@ -444,6 +449,8 @@ def valid_result(self, r: Dict) -> bool:
return True

def load_previous_results(self) -> None:
self.load_previous_results_from_sarif()

filename = self._previous_results_filename
try:
if os.path.isfile(filename):
Expand All @@ -453,9 +460,24 @@ def load_previous_results(self) -> None:
for r in self._previous_results:
if "id" in r:
self._previous_results_ids.add(r["id"])

except json.decoder.JSONDecodeError:
logger.error(red(f"Impossible to decode {filename}. Consider removing the file"))

def load_previous_results_from_sarif(self) -> None:
sarif = pathlib.Path(self.sarif_input)
triage = pathlib.Path(self.sarif_triage)

if not sarif.exists():
return
if not triage.exists():
return

triaged = read_triage_info(sarif, triage)

for id_triaged in triaged:
self._previous_results_ids.add(id_triaged)

def write_results_to_hide(self) -> None:
if not self._results_to_hide:
return
Expand Down
19 changes: 10 additions & 9 deletions slither/printers/guidance/echidna.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
TypeConversion,
)
from slither.slithir.operations.binary import Binary
from slither.slithir.variables import Constant
from slither.slithir.variables import Constant, ReferenceVariable
from slither.utils.output import Output
from slither.visitors.expression.constants_folding import ConstantFolding, NotConstant

Expand Down Expand Up @@ -217,19 +217,20 @@ def _extract_constants_from_irs( # pylint: disable=too-many-branches,too-many-n
except ValueError: # index could fail; should never happen in working solidity code
pass
for r in ir.read:
var_read = r.points_to_origin if isinstance(r, ReferenceVariable) else r
# Do not report struct_name in a.struct_name
if isinstance(ir, Member):
continue
if isinstance(r, Constant):
all_cst_used.append(ConstantValue(str(r.value), str(r.type)))
if isinstance(r, StateVariable):
if r.node_initialization:
if r.node_initialization.irs:
if r.node_initialization in context_explored:
if isinstance(var_read, Constant):
all_cst_used.append(ConstantValue(str(var_read.value), str(var_read.type)))
if isinstance(var_read, StateVariable):
if var_read.node_initialization:
if var_read.node_initialization.irs:
if var_read.node_initialization in context_explored:
continue
context_explored.add(r.node_initialization)
context_explored.add(var_read.node_initialization)
_extract_constants_from_irs(
r.node_initialization.irs,
var_read.node_initialization.irs,
all_cst_used,
all_cst_used_in_binary,
context_explored,
Expand Down
2 changes: 2 additions & 0 deletions slither/solc_parsing/declarations/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,8 @@ def parse_state_variables(self) -> None:
self._variables_parser.append(var_parser)

assert var.name
if var_parser.reference_id is not None:
self._contract.state_variables_by_ref_id[var_parser.reference_id] = var
self._contract.variables_as_dict[var.name] = var
self._contract.add_variables_ordered([var])

Expand Down
14 changes: 13 additions & 1 deletion slither/solc_parsing/declarations/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,9 @@ def analyze_content(self) -> None:

self._remove_alone_endif()

if self._function.entry_point:
self._update_reachability(self._function.entry_point)

# endregion
###################################################################################
###################################################################################
Expand Down Expand Up @@ -983,7 +986,9 @@ def _parse_statement(
# technically, entrypoint and exitpoint are YulNodes and we should be returning a NodeSolc here
# but they both expose an underlying_node so oh well
link_underlying_nodes(node, entrypoint)
node = exitpoint
end_assembly = self._new_node(NodeType.ENDASSEMBLY, statement["src"], scope)
link_underlying_nodes(exitpoint, end_assembly)
node = end_assembly
else:
asm_node = self._new_node(NodeType.ASSEMBLY, statement["src"], scope)
self._function.contains_assembly = True
Expand Down Expand Up @@ -1100,6 +1105,13 @@ def _parse_unchecked_block(self, block: Dict, node: NodeSolc, scope):
node = self._parse_statement(statement, node, new_scope)
return node

def _update_reachability(self, node: Node) -> None:
if node.is_reachable:
return
node.set_is_reachable(True)
for son in node.sons:
self._update_reachability(son)

def _parse_cfg(self, cfg: Dict) -> None:

assert cfg[self.get_key()] == "Block"
Expand Down
9 changes: 5 additions & 4 deletions slither/solc_parsing/expressions/expression_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,13 +487,18 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)

t = None

referenced_declaration = None
if caller_context.is_compact_ast:
value = expression["name"]
t = expression["typeDescriptions"]["typeString"]
if "referencedDeclaration" in expression:
referenced_declaration = expression["referencedDeclaration"]
else:
value = expression["attributes"]["value"]
if "type" in expression["attributes"]:
t = expression["attributes"]["type"]
if "referencedDeclaration" in expression["attributes"]:
referenced_declaration = expression["attributes"]["referencedDeclaration"]

if t:
found = re.findall(r"[struct|enum|function|modifier] \(([\[\] ()a-zA-Z0-9\.,_]*)\)", t)
Expand All @@ -502,10 +507,6 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
value = value + "(" + found[0] + ")"
value = filter_name(value)

if "referencedDeclaration" in expression:
referenced_declaration = expression["referencedDeclaration"]
else:
referenced_declaration = None
var, was_created = find_variable(value, caller_context, referenced_declaration)
if was_created:
var.set_offset(src, caller_context.compilation_unit)
Expand Down
55 changes: 35 additions & 20 deletions slither/solc_parsing/expressions/find_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,24 @@ def _find_variable_from_ref_declaration(
referenced_declaration: Optional[int],
all_contracts: List["Contract"],
all_functions: List["Function"],
function_parser: Optional["FunctionSolc"],
contract_declarer: Optional["Contract"],
) -> Optional[Union[Contract, Function]]:
"""
Reference declarations take the highest priority, but they are not available for legacy AST.
"""
if referenced_declaration is None:
return None
# id of the contracts is the referenced declaration
# We look for variable declared with the referencedDeclaration attribute
if function_parser is not None and referenced_declaration in function_parser.variables_renamed:
return function_parser.variables_renamed[referenced_declaration].underlying_variable

if (
contract_declarer is not None
and referenced_declaration in contract_declarer.state_variables_by_ref_id
):
return contract_declarer.state_variables_by_ref_id[referenced_declaration]
# Ccontracts ids are the referenced declaration
# This is not true for the functions, as we dont always have the referenced_declaration
# But maybe we could? (TODO)
for contract_candidate in all_contracts:
Expand All @@ -74,14 +88,9 @@ def _find_variable_from_ref_declaration(
def _find_variable_in_function_parser(
var_name: str,
function_parser: Optional["FunctionSolc"],
referenced_declaration: Optional[int] = None,
) -> Optional[Variable]:
if function_parser is None:
return None
# We look for variable declared with the referencedDeclaration attr
func_variables_renamed = function_parser.variables_renamed
if referenced_declaration and referenced_declaration in func_variables_renamed:
return func_variables_renamed[referenced_declaration].underlying_variable
# If not found, check for name
func_variables = function_parser.underlying_function.variables_as_dict
if var_name in func_variables:
Expand Down Expand Up @@ -391,20 +400,6 @@ def find_variable(
if var_name in current_scope.renaming:
var_name = current_scope.renaming[var_name]

# Use ret0/ret1 to help mypy
ret0 = _find_variable_from_ref_declaration(
referenced_declaration, direct_contracts, direct_functions
)
if ret0:
return ret0, False

function_parser: Optional[FunctionSolc] = (
caller_context if isinstance(caller_context, FunctionSolc) else None
)
ret1 = _find_variable_in_function_parser(var_name, function_parser, referenced_declaration)
if ret1:
return ret1, False

contract: Optional[Contract] = None
contract_declarer: Optional[Contract] = None
if isinstance(caller_context, ContractSolc):
Expand All @@ -427,6 +422,24 @@ def find_variable(
if var_name == var.name:
return var, False

function_parser: Optional[FunctionSolc] = (
caller_context if isinstance(caller_context, FunctionSolc) else None
)
# Use ret0/ret1 to help mypy
ret0 = _find_variable_from_ref_declaration(
referenced_declaration,
direct_contracts,
direct_functions,
function_parser,
contract_declarer,
)
if ret0:
return ret0, False

ret1 = _find_variable_in_function_parser(var_name, function_parser)
if ret1:
return ret1, False

ret = _find_in_contract(var_name, contract, contract_declarer, is_super, is_identifier_path)
if ret:
return ret, False
Expand Down Expand Up @@ -477,6 +490,8 @@ def find_variable(
referenced_declaration,
list(current_scope.contracts.values()),
list(current_scope.functions),
None,
None,
)
if ret:
return ret, False
Expand Down
Loading

0 comments on commit 2d2c77a

Please sign in to comment.