Merge pull request #480 from crytic/dev-fixpoint-are-variables-written

Add better fixpoint on are_variables_written
pull/483/head
Feist Josselin 5 years ago committed by GitHub
commit bfaa9a51ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 64
      slither/analyses/write/are_variables_written.py

@ -1,21 +1,46 @@
""" """
Detect if all the given variables are written in all the paths of the function Detect if all the given variables are written in all the paths of the function
""" """
from slither.core.cfg.node import NodeType from collections import defaultdict
from typing import Dict, Tuple, Set, List, Optional
from slither.core.cfg.node import NodeType, Node
from slither.core.declarations import SolidityFunction from slither.core.declarations import SolidityFunction
from slither.core.variables.variable import Variable
from slither.slithir.operations import (Index, Member, OperationWithLValue, from slither.slithir.operations import (Index, Member, OperationWithLValue,
SolidityCall, Length, Balance) SolidityCall, Length, Balance)
from slither.slithir.variables import ReferenceVariable from slither.slithir.variables import ReferenceVariable, TemporaryVariable
class State:
def _visit(node, visited, variables_written, variables_to_write): def __init__(self):
# Map node -> list of variables set
# Were each variables set represents a configuration of a path
# If two paths lead to the exact same set of variables written, we dont need to explore both
# We need to keep different set per path, because we want to capture stuff like
# if (..){
# v = 10
# }
# Here in the endIF node, v can be written, or can be not written. If we were merging the paths
# We would lose this information
# In other words, in each in the list represents a set of path that has the same outcome
self.nodes: Dict[Node, List[Set[Variable]]] = defaultdict(list)
if node in visited:
return []
visited = visited + [node] def _visit(node: Node, state: State, variables_written: Set[Variable], variables_to_write: List[Variable]):
"""
Explore all the nodes to look for values not written when the node's function return
Fixpoint reaches if no new written variables are found
:param node:
:param state:
:param variables_to_write:
:return:
"""
refs = {} refs = {}
variables_written = set(variables_written)
for ir in node.irs: for ir in node.irs:
if isinstance(ir, SolidityCall): if isinstance(ir, SolidityCall):
# TODO convert the revert to a THROW node # TODO convert the revert to a THROW node
@ -30,22 +55,33 @@ def _visit(node, visited, variables_written, variables_to_write):
if isinstance(ir, (Length, Balance)): if isinstance(ir, (Length, Balance)):
refs[ir.lvalue] = ir.value refs[ir.lvalue] = ir.value
variables_written = variables_written + [ir.lvalue] if ir.lvalue and not isinstance(ir.lvalue, (TemporaryVariable, ReferenceVariable)):
variables_written.add(ir.lvalue)
lvalue = ir.lvalue lvalue = ir.lvalue
while isinstance(lvalue, ReferenceVariable): while isinstance(lvalue, ReferenceVariable):
if lvalue not in refs: if lvalue not in refs:
break break
variables_written = variables_written + [refs[lvalue]] if refs[lvalue] and not isinstance(refs[lvalue], (TemporaryVariable, ReferenceVariable)):
variables_written.add(refs[lvalue])
lvalue = refs[lvalue] lvalue = refs[lvalue]
ret = [] ret = []
if not node.sons and not node.type in [NodeType.THROW, NodeType.RETURN]: if not node.sons and node.type not in [NodeType.THROW, NodeType.RETURN]:
ret += [v for v in variables_to_write if not v in variables_written] ret += [v for v in variables_to_write if v not in variables_written]
for son in node.sons: # Explore sons if
ret += _visit(son, visited, variables_written, variables_to_write) # - Before is none: its the first time we explored the node
# - variables_written is not before: it means that this path has a configuration of set variables
# that we haven't seen yet
before = state.nodes[node] if node in state.nodes else None
if before is None or variables_written not in before:
state.nodes[node].append(variables_written)
for son in node.sons:
ret += _visit(son, state, variables_written, variables_to_write)
return ret return ret
def are_variables_written(function, variables_to_write): def are_variables_written(function, variables_to_write):
""" """
Return the list of variable that are not written at the end of the function Return the list of variable that are not written at the end of the function
@ -56,4 +92,4 @@ def are_variables_written(function, variables_to_write):
Returns: Returns:
list(Variable): List of variable that are not written (sublist of variables_to_write) list(Variable): List of variable that are not written (sublist of variables_to_write)
""" """
return list(set(_visit(function.entry_point, [], [], variables_to_write))) return list(set(_visit(function.entry_point, State(), set(), variables_to_write)))

Loading…
Cancel
Save