diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 79e935e74..9626a4a87 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -5,6 +5,7 @@ import logging from slither.core.source_mapping.source_mapping import SourceMapping from slither.core.variables.variable import Variable +from slither.core.variables.state_variable import StateVariable from slither.visitors.expression.expression_printer import ExpressionPrinter from slither.visitors.expression.read_var import ReadVar @@ -12,10 +13,17 @@ from slither.visitors.expression.write_var import WriteVar from slither.core.children.child_function import ChildFunction -from slither.core.declarations.solidity_variables import SolidityFunction +from slither.core.declarations.solidity_variables import SolidityVariable, SolidityFunction from slither.slithir.convert import convert_expression +from slither.slithir.operations import OperationWithLValue, Index, Member, LowLevelCall, SolidityCall, HighLevelCall, InternalCall, LibraryCall + + +from slither.slithir.variables import Constant, ReferenceVariable, TemporaryVariable, TupleVariable + +from slither.core.declarations import Contract + logger = logging.getLogger("Node") class NodeType: @@ -103,7 +111,10 @@ class Node(SourceMapping, ChildFunction): self._vars_written = [] self._vars_read = [] self._internal_calls = [] - self._external_calls = [] + self._solidity_calls = [] + self._high_level_calls = [] + self._low_level_calls = [] + self._external_calls_as_expressions = [] self._irs = [] self._state_vars_written = [] @@ -176,16 +187,40 @@ class Node(SourceMapping, ChildFunction): @property def internal_calls(self): """ - list(Function or SolidityFunction): List of function calls (that does not create a transaction) + list(Function or SolidityFunction): List of internal/soldiity function calls """ return list(self._internal_calls) @property - def external_calls(self): + def solidity_calls(self): + """ + list(SolidityFunction): List of Soldity calls + """ + return list(self._internal_calls) + + @property + def high_level_calls(self): + """ + list((Contract, Function)): List of high level calls (external calls). Include library calls + """ + return list(self._high_level_calls) + + @property + def low_level_calls(self): + """ + list((Variable|SolidityVariable, str)): List of low_level call + A low level call is defined by + - the variable called + - the name of the function (call/delegatecall/codecall) + """ + return list(self._low_level_calls) + + @property + def external_calls_as_expressions(self): """ list(CallExpression): List of message calls (that creates a transaction) """ - return self._external_calls + return self._external_calls_as_expressions @property def calls_as_expression(self): @@ -226,10 +261,7 @@ class Node(SourceMapping, ChildFunction): Returns: bool: True if the node has a require or assert call """ - return self.internal_calls and\ - any(isinstance(c, SolidityFunction) and\ - (c.name in ['require(bool)', 'require(bool,string)', 'assert(bool)'])\ - for c in self.internal_calls) + return any(c.name in ['require(bool)', 'require(bool,string)', 'assert(bool)'] for c in self.internal_calls) def contains_if(self): """ @@ -328,3 +360,55 @@ class Node(SourceMapping, ChildFunction): if self.expression: expression = self.expression self._irs = convert_expression(expression, self) + + self._find_read_write_call() + + def _find_read_write_call(self): + + def is_slithir_var(var): + return isinstance(var, (Constant, ReferenceVariable, TemporaryVariable, TupleVariable)) + + for ir in self.irs: + self._vars_read += [v for v in ir.read if not is_slithir_var(v)] + if isinstance(ir, OperationWithLValue): + if isinstance(ir, (Index, Member)): + continue # Don't consider Member and Index operations -> ReferenceVariable + var = ir.lvalue + # If its a reference, we loop until finding the origin + if isinstance(var, (ReferenceVariable)): + while isinstance(var, ReferenceVariable): + var = var.points_to + # Only store non-slithIR variables + if not is_slithir_var(var): + self._vars_written.append(var) + + if isinstance(ir, InternalCall): + self._internal_calls.append(ir.function) + if isinstance(ir, SolidityCall): + # TODO: consider removing dependancy of solidity_call to internal_call + self._solidity_calls.append(ir.function) + self._internal_calls.append(ir.function) + if isinstance(ir, LowLevelCall): + assert isinstance(ir.destination, (Variable, SolidityVariable)) + self._low_level_calls.append((ir.destination, ir.function_name.value)) + elif isinstance(ir, (HighLevelCall)) and not isinstance(ir, LibraryCall): + if isinstance(ir.destination.type, Contract): + self._high_level_calls.append((ir.destination.type, ir.function)) + else: + self._high_level_calls.append((ir.destination.type.type, ir.function)) + elif isinstance(ir, LibraryCall): + assert isinstance(ir.destination, Contract) + self._high_level_calls.append((ir.destination, ir.function)) + + self._vars_read = list(set(self._vars_read)) + self._state_vars_read = [v for v in self._vars_read if isinstance(v, StateVariable)] + self._solidity_vars_read = [v for v in self._vars_read if isinstance(v, SolidityVariable)] + self._vars_written = list(set(self._vars_written)) + self._state_vars_written = [v for v in self._vars_written if isinstance(v, StateVariable)] + self._internal_calls = list(set(self._internal_calls)) + self._solidity_calls = list(set(self._solidity_calls)) + self._high_level_calls = list(set(self._high_level_calls)) + self._low_level_calls = list(set(self._low_level_calls)) + + + diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 6e71ddfc7..c3551cd19 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -44,7 +44,10 @@ class Function(ChildContract, SourceMapping): self._solidity_vars_read = [] self._state_vars_written = [] self._internal_calls = [] - self._external_calls = [] + self._solidity_calls = [] + self._low_level_calls = [] + self._high_level_calls = [] + self._external_calls_as_expressions = [] self._expression_vars_read = [] self._expression_vars_written = [] self._expression_calls = [] @@ -239,14 +242,39 @@ class Function(ChildContract, SourceMapping): return list(self._internal_calls) @property - def external_calls(self): + def solidity_calls(self): + """ + list(SolidityFunction): List of Soldity calls + """ + return list(self._internal_calls) + + @property + def high_level_calls(self): + """ + list((Contract, Function)): List of high level calls (external calls). Include library calls + """ + return list(self._high_level_calls) + + @property + def low_level_calls(self): + """ + list((Variable|SolidityVariable, str)): List of low_level call + A low level call is defined by + - the variable called + - the name of the function (call/delegatecall/codecall) + """ + return list(self._low_level_calls) + + + @property + def external_calls_as_expressions(self): """ list(ExpressionCall): List of message calls (that creates a transaction) """ - return list(self._external_calls) + return list(self._external_calls_as_expressions) @property - def calls_as_expression(self): + def calls_as_expressions(self): return self._expression_calls @property @@ -353,6 +381,7 @@ class Function(ChildContract, SourceMapping): calls = [x for x in calls if x] calls = [item for sublist in calls for item in sublist] # Remove dupplicate if they share the same string representation + # TODO: check if groupby is still necessary here calls = [next(obj) for i, obj in\ groupby(sorted(calls, key=lambda x: str(x)), lambda x: str(x))] self._expression_calls = calls @@ -364,12 +393,30 @@ class Function(ChildContract, SourceMapping): groupby(sorted(internal_calls, key=lambda x: str(x)), lambda x: str(x))] self._internal_calls = internal_calls - external_calls = [x.external_calls for x in self.nodes] - external_calls = [x for x in external_calls if x] - external_calls = [item for sublist in external_calls for item in sublist] - external_calls = [next(obj) for i, obj in - groupby(sorted(external_calls, key=lambda x: str(x)), lambda x: str(x))] - self._external_calls = external_calls + self._solidity_calls = [c for c in internal_calls if isinstance(c, SolidityFunction)] + + low_level_calls = [x.low_level_calls for x in self.nodes] + low_level_calls = [x for x in low_level_calls if x] + low_level_calls = [item for sublist in low_level_calls for item in sublist] + low_level_calls = [next(obj) for i, obj in + groupby(sorted(low_level_calls, key=lambda x: str(x)), lambda x: str(x))] + + self._low_level_calls = low_level_calls + + high_level_calls = [x.high_level_calls for x in self.nodes] + high_level_calls = [x for x in high_level_calls if x] + high_level_calls = [item for sublist in high_level_calls for item in sublist] + high_level_calls = [next(obj) for i, obj in + groupby(sorted(high_level_calls, key=lambda x: str(x)), lambda x: str(x))] + + self._high_level_calls = high_level_calls + + external_calls_as_expressions = [x.external_calls_as_expressions for x in self.nodes] + external_calls_as_expressions = [x for x in external_calls_as_expressions if x] + external_calls_as_expressions = [item for sublist in external_calls_as_expressions for item in sublist] + external_calls_as_expressions = [next(obj) for i, obj in + groupby(sorted(external_calls_as_expressions, key=lambda x: str(x)), lambda x: str(x))] + self._external_calls_as_expressions = external_calls_as_expressions def _explore_functions(self, f_new_values): @@ -567,14 +614,14 @@ class Function(ChildContract, SourceMapping): Return the function summary Returns: (str, str, list(str), list(str), listr(str), list(str), list(str); - name, visibility, modifiers, vars read, vars written, internal_calls, external_calls + name, visibility, modifiers, vars read, vars written, internal_calls, external_calls_as_expressions """ return (self.name, self.visibility, [str(x) for x in self.modifiers], [str(x) for x in self.state_variables_read + self.solidity_variables_read], [str(x) for x in self.state_variables_written], [str(x) for x in self.internal_calls], - [str(x) for x in self.external_calls]) + [str(x) for x in self.external_calls_as_expressions]) def is_protected(self): """ diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 990ea756f..3fca07f06 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -544,8 +544,14 @@ def remove_temporary(result): return result def remove_unused(result): - removed = True + + if not result: + return result + + # dont remove the last elem, as it may be used by RETURN + last_elem = result[-1] + while removed: removed = False @@ -562,7 +568,7 @@ def remove_unused(result): for ins in result: if isinstance(ins, Member): - if not ins.lvalue.name in to_keep: + if not ins.lvalue.name in to_keep and ins != last_elem: to_remove.append(ins) removed = True diff --git a/slither/solc_parsing/cfg/node.py b/slither/solc_parsing/cfg/node.py index f72e7aaa7..56e73e32c 100644 --- a/slither/solc_parsing/cfg/node.py +++ b/slither/solc_parsing/cfg/node.py @@ -45,25 +45,21 @@ class NodeSolc(Node): expression = self.expression pp = ReadVar(expression) self._expression_vars_read = pp.result() - vars_read = [ExportValues(v).result() for v in self._expression_vars_read] - self._vars_read = [item for sublist in vars_read for item in sublist] - self._state_vars_read = [x for x in self.variables_read if\ - isinstance(x, (StateVariable))] - self._solidity_vars_read = [x for x in self.variables_read if\ - isinstance(x, (SolidityVariable))] + +# self._vars_read = [item for sublist in vars_read for item in sublist] +# self._state_vars_read = [x for x in self.variables_read if\ +# isinstance(x, (StateVariable))] +# self._solidity_vars_read = [x for x in self.variables_read if\ +# isinstance(x, (SolidityVariable))] pp = WriteVar(expression) self._expression_vars_written = pp.result() - vars_written = [ExportValues(v).result() for v in self._expression_vars_written] - self._vars_written = [item for sublist in vars_written for item in sublist] - self._state_vars_written = [x for x in self.variables_written if\ - isinstance(x, StateVariable)] + +# self._vars_written = [item for sublist in vars_written for item in sublist] +# self._state_vars_written = [x for x in self.variables_written if\ +# isinstance(x, StateVariable)] pp = FindCalls(expression) self._expression_calls = pp.result() - calls = [ExportValues(c).result() for c in self.calls_as_expression] - calls = [item for sublist in calls for item in sublist] - self._internal_calls = [c for c in calls if isinstance(c, (Function, SolidityFunction))] - - self._external_calls = [c for c in self.calls_as_expression if not isinstance(c.called, Identifier)] + self._external_calls_as_expressions = [c for c in self.calls_as_expression if not isinstance(c.called, Identifier)] diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index 407acdd29..021444360 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -630,13 +630,13 @@ class FunctionSolc(Function): break self._remove_alone_endif() - self._analyze_read_write() - self._analyze_calls() def convert_expression_to_slithir(self): for node in self.nodes: node.slithir_generation() transform_slithir_vars_to_ssa(self) + self._analyze_read_write() + self._analyze_calls() def split_ternary_node(self, node, condition, true_expr, false_expr):