Merge branch 'dev' of github.com:crytic/slither into dev

pull/1559/head^2
Josselin Feist 2 years ago
commit 4f0b74beed
  1. 6
      slither/core/compilation_unit.py
  2. 3
      slither/core/declarations/__init__.py
  3. 22
      slither/core/declarations/contract.py
  4. 18
      slither/core/declarations/using_for_top_level.py
  5. 5
      slither/core/scope/scope.py
  6. 70
      slither/slithir/convert.py
  7. 52
      slither/solc_parsing/declarations/contract.py
  8. 4
      slither/solc_parsing/declarations/function.py
  9. 2
      slither/solc_parsing/declarations/modifier.py
  10. 153
      slither/solc_parsing/declarations/using_for_top_level.py
  11. 41
      slither/solc_parsing/slither_compilation_unit_solc.py
  12. 8
      slither/solc_parsing/solidity_types/type_parsing.py
  13. 109
      slither/tools/upgradeability/__main__.py
  14. 174
      slither/utils/expression_manipulations.py
  15. BIN
      tests/ast-parsing/compile/using-for-1-0.8.0.sol-0.8.15-compact.zip
  16. BIN
      tests/ast-parsing/compile/using-for-2-0.8.0.sol-0.8.15-compact.zip
  17. BIN
      tests/ast-parsing/compile/using-for-3-0.8.0.sol-0.8.15-compact.zip
  18. BIN
      tests/ast-parsing/compile/using-for-4-0.8.0.sol-0.8.15-compact.zip
  19. BIN
      tests/ast-parsing/compile/using-for-functions-list-1-0.8.0.sol-0.8.15-compact.zip
  20. BIN
      tests/ast-parsing/compile/using-for-functions-list-2-0.8.0.sol-0.8.15-compact.zip
  21. BIN
      tests/ast-parsing/compile/using-for-functions-list-3-0.8.0.sol-0.8.15-compact.zip
  22. BIN
      tests/ast-parsing/compile/using-for-functions-list-4-0.8.0.sol-0.8.15-compact.zip
  23. BIN
      tests/ast-parsing/compile/using-for-global-0.8.0.sol-0.8.15-compact.zip
  24. 10
      tests/ast-parsing/expected/using-for-1-0.8.0.sol-0.8.15-compact.json
  25. 10
      tests/ast-parsing/expected/using-for-2-0.8.0.sol-0.8.15-compact.json
  26. 8
      tests/ast-parsing/expected/using-for-3-0.8.0.sol-0.8.15-compact.json
  27. 8
      tests/ast-parsing/expected/using-for-4-0.8.0.sol-0.8.15-compact.json
  28. 10
      tests/ast-parsing/expected/using-for-functions-list-1-0.8.0.sol-0.8.15-compact.json
  29. 10
      tests/ast-parsing/expected/using-for-functions-list-2-0.8.0.sol-0.8.15-compact.json
  30. 10
      tests/ast-parsing/expected/using-for-functions-list-3-0.8.0.sol-0.8.15-compact.json
  31. 10
      tests/ast-parsing/expected/using-for-functions-list-4-0.8.0.sol-0.8.15-compact.json
  32. 11
      tests/ast-parsing/expected/using-for-global-0.8.0.sol-0.8.15-compact.json
  33. 40
      tests/ast-parsing/using-for-1-0.8.0.sol
  34. 40
      tests/ast-parsing/using-for-2-0.8.0.sol
  35. 27
      tests/ast-parsing/using-for-3-0.8.0.sol
  36. 25
      tests/ast-parsing/using-for-4-0.8.0.sol
  37. 37
      tests/ast-parsing/using-for-functions-list-1-0.8.0.sol
  38. 41
      tests/ast-parsing/using-for-functions-list-2-0.8.0.sol
  39. 41
      tests/ast-parsing/using-for-functions-list-3-0.8.0.sol
  40. 40
      tests/ast-parsing/using-for-functions-list-4-0.8.0.sol
  41. 15
      tests/ast-parsing/using-for-global-0.8.0.sol
  42. 32
      tests/ast-parsing/using-for-library-0.8.0.sol
  43. 18
      tests/slithir/ternary_expressions.sol
  44. 4
      tests/slithir/test_ternary_expressions.py
  45. 9
      tests/test_ast_parsing.py
  46. 23
      tests/test_features.py

@ -16,6 +16,7 @@ from slither.core.declarations import (
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.using_for_top_level import UsingForTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel
from slither.core.scope.scope import FileScope
from slither.core.variables.state_variable import StateVariable
@ -41,6 +42,7 @@ class SlitherCompilationUnit(Context):
self._enums_top_level: List[EnumTopLevel] = []
self._variables_top_level: List[TopLevelVariable] = []
self._functions_top_level: List[FunctionTopLevel] = []
self._using_for_top_level: List[UsingForTopLevel] = []
self._pragma_directives: List[Pragma] = []
self._import_directives: List[Import] = []
self._custom_errors: List[CustomError] = []
@ -205,6 +207,10 @@ class SlitherCompilationUnit(Context):
def functions_top_level(self) -> List[FunctionTopLevel]:
return self._functions_top_level
@property
def using_for_top_level(self) -> List[UsingForTopLevel]:
return self._using_for_top_level
@property
def custom_errors(self) -> List[CustomError]:
return self._custom_errors

@ -12,5 +12,8 @@ from .solidity_variables import (
)
from .structure import Structure
from .enum_contract import EnumContract
from .enum_top_level import EnumTopLevel
from .structure_contract import StructureContract
from .structure_top_level import StructureTopLevel
from .function_contract import FunctionContract
from .function_top_level import FunctionTopLevel

@ -79,6 +79,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
# The only str is "*"
self._using_for: Dict[Union[str, Type], List[Type]] = {}
self._using_for_complete: Dict[Union[str, Type], List[Type]] = None
self._kind: Optional[str] = None
self._is_interface: bool = False
self._is_library: bool = False
@ -266,6 +267,27 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def using_for(self) -> Dict[Union[str, Type], List[Type]]:
return self._using_for
@property
def using_for_complete(self) -> Dict[Union[str, Type], List[Type]]:
"""
Dict[Union[str, Type], List[Type]]: Dict of merged local using for directive with top level directive
"""
def _merge_using_for(uf1, uf2):
result = {**uf1, **uf2}
for key, value in result.items():
if key in uf1 and key in uf2:
result[key] = value + uf1[key]
return result
if self._using_for_complete is None:
result = self.using_for
top_level_using_for = self.file_scope.using_for_directives
for uftl in top_level_using_for:
result = _merge_using_for(result, uftl.using_for)
self._using_for_complete = result
return self._using_for_complete
# endregion
###################################################################################
###################################################################################

@ -0,0 +1,18 @@
from typing import TYPE_CHECKING, List, Dict, Union
from slither.core.solidity_types.type import Type
from slither.core.declarations.top_level import TopLevel
if TYPE_CHECKING:
from slither.core.scope.scope import FileScope
class UsingForTopLevel(TopLevel):
def __init__(self, scope: "FileScope"):
super().__init__()
self._using_for: Dict[Union[str, Type], List[Type]] = {}
self.file_scope: "FileScope" = scope
@property
def using_for(self) -> Dict[Type, List[Type]]:
return self._using_for

@ -8,6 +8,7 @@ from slither.core.declarations import Contract, Import, Pragma
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.using_for_top_level import UsingForTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel
from slither.core.solidity_types import TypeAlias
from slither.core.variables.top_level_variable import TopLevelVariable
@ -38,6 +39,7 @@ class FileScope:
# Because we parse the function signature later on
# So we simplify the logic and have the scope fields all populated
self.functions: Set[FunctionTopLevel] = set()
self.using_for_directives: Set[UsingForTopLevel] = set()
self.imports: Set[Import] = set()
self.pragmas: Set[Pragma] = set()
self.structures: Dict[str, StructureTopLevel] = {}
@ -75,6 +77,9 @@ class FileScope:
if not new_scope.functions.issubset(self.functions):
self.functions |= new_scope.functions
learn_something = True
if not new_scope.using_for_directives.issubset(self.using_for_directives):
self.using_for_directives |= new_scope.using_for_directives
learn_something = True
if not new_scope.imports.issubset(self.imports):
self.imports |= new_scope.imports
learn_something = True

@ -15,6 +15,7 @@ from slither.core.declarations import (
)
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.function_contract import FunctionContract
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder
from slither.core.declarations.solidity_variables import SolidityCustomRevert
from slither.core.expressions import Identifier, Literal
@ -199,18 +200,22 @@ def _fits_under_byte(val: Union[int, str]) -> List[str]:
return [f"bytes{f}" for f in range(length, 33)] + ["bytes"]
def _find_function_from_parameter(ir: Call, candidates: List[Function]) -> Optional[Function]:
def _find_function_from_parameter(
arguments: List[Variable], candidates: List[Function], full_comparison: bool
) -> Optional[Function]:
"""
Look for a function in candidates that can be the target of the ir's call
Look for a function in candidates that can be the target based on the ir's call arguments
Try the implicit type conversion for uint/int/bytes. Constant values can be both uint/int
While variables stick to their base type, but can changed the size
While variables stick to their base type, but can changed the size.
If full_comparison is True it will do a comparison of all the arguments regardless if
the candidate remained is one.
:param ir:
:param arguments:
:param candidates:
:param full_comparison:
:return:
"""
arguments = ir.arguments
type_args: List[str]
for idx, arg in enumerate(arguments):
if isinstance(arg, (list,)):
@ -258,7 +263,7 @@ def _find_function_from_parameter(ir: Call, candidates: List[Function]) -> Optio
not_found = False
candidates_kept.append(candidate)
if len(candidates_kept) == 1:
if len(candidates_kept) == 1 and not full_comparison:
return candidates_kept[0]
candidates = candidates_kept
if len(candidates) == 1:
@ -503,7 +508,9 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals
# propagate the type
node_function = node.function
using_for = (
node_function.contract.using_for if isinstance(node_function, FunctionContract) else {}
node_function.contract.using_for_complete
if isinstance(node_function, FunctionContract)
else {}
)
if isinstance(ir, OperationWithLValue):
# Force assignment in case of missing previous correct type
@ -530,9 +537,9 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals
if can_be_solidity_func(ir):
return convert_to_solidity_func(ir)
# convert library
# convert library or top level function
if t in using_for or "*" in using_for:
new_ir = convert_to_library(ir, node, using_for)
new_ir = convert_to_library_or_top_level(ir, node, using_for)
if new_ir:
return new_ir
@ -881,7 +888,9 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]): # pylint: dis
# }
node_func = ins.node.function
using_for = (
node_func.contract.using_for if isinstance(node_func, FunctionContract) else {}
node_func.contract.using_for_complete
if isinstance(node_func, FunctionContract)
else {}
)
targeted_libraries = (
@ -894,6 +903,10 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]): # pylint: dis
lib_contract_type.type, Contract
):
continue
if isinstance(lib_contract_type, FunctionContract):
# Using for with list of functions, this is the function called
candidates.append(lib_contract_type)
else:
lib_contract = lib_contract_type.type
for lib_func in lib_contract.functions:
if lib_func.name == ins.ori.variable_right:
@ -1326,8 +1339,31 @@ def convert_to_pop(ir, node):
return ret
def look_for_library(contract, ir, using_for, t):
def look_for_library_or_top_level(contract, ir, using_for, t):
for destination in using_for[t]:
if isinstance(destination, FunctionTopLevel) and destination.name == ir.function_name:
arguments = [ir.destination] + ir.arguments
if (
len(destination.parameters) == len(arguments)
and _find_function_from_parameter(arguments, [destination], True) is not None
):
internalcall = InternalCall(destination, ir.nbr_arguments, ir.lvalue, ir.type_call)
internalcall.set_expression(ir.expression)
internalcall.set_node(ir.node)
internalcall.arguments = [ir.destination] + ir.arguments
return_type = internalcall.function.return_type
if return_type:
if len(return_type) == 1:
internalcall.lvalue.set_type(return_type[0])
elif len(return_type) > 1:
internalcall.lvalue.set_type(return_type)
else:
internalcall.lvalue = None
return internalcall
if isinstance(destination, FunctionContract) and destination.contract.is_library:
lib_contract = destination.contract
else:
lib_contract = contract.file_scope.get_contract_from_name(str(destination))
if lib_contract:
lib_call = LibraryCall(
@ -1348,19 +1384,19 @@ def look_for_library(contract, ir, using_for, t):
return None
def convert_to_library(ir, node, using_for):
def convert_to_library_or_top_level(ir, node, using_for):
# We use contract_declarer, because Solidity resolve the library
# before resolving the inheritance.
# Though we could use .contract as libraries cannot be shadowed
contract = node.function.contract_declarer
t = ir.destination.type
if t in using_for:
new_ir = look_for_library(contract, ir, using_for, t)
new_ir = look_for_library_or_top_level(contract, ir, using_for, t)
if new_ir:
return new_ir
if "*" in using_for:
new_ir = look_for_library(contract, ir, using_for, "*")
new_ir = look_for_library_or_top_level(contract, ir, using_for, "*")
if new_ir:
return new_ir
@ -1406,7 +1442,7 @@ def convert_type_library_call(ir: HighLevelCall, lib_contract: Contract):
# TODO: handle collision with multiple state variables/functions
func = lib_contract.get_state_variable_from_name(ir.function_name)
if func is None and candidates:
func = _find_function_from_parameter(ir, candidates)
func = _find_function_from_parameter(ir.arguments, candidates, False)
# In case of multiple binding to the same type
# TODO: this part might not be needed with _find_function_from_parameter
@ -1502,7 +1538,7 @@ def convert_type_of_high_and_internal_level_call(ir: Operation, contract: Option
if f.name == ir.function_name and len(f.parameters) == len(ir.arguments)
]
func = _find_function_from_parameter(ir, candidates)
func = _find_function_from_parameter(ir.arguments, candidates, False)
if not func:
assert contract
@ -1525,7 +1561,7 @@ def convert_type_of_high_and_internal_level_call(ir: Operation, contract: Option
# TODO: handle collision with multiple state variables/functions
func = contract.get_state_variable_from_name(ir.function_name)
if func is None and candidates:
func = _find_function_from_parameter(ir, candidates)
func = _find_function_from_parameter(ir.arguments, candidates, False)
# lowlelvel lookup needs to be done at last step
if not func:

@ -6,7 +6,7 @@ from slither.core.declarations import Modifier, Event, EnumContract, StructureCo
from slither.core.declarations.contract import Contract
from slither.core.declarations.custom_error_contract import CustomErrorContract
from slither.core.declarations.function_contract import FunctionContract
from slither.core.solidity_types import ElementaryType, TypeAliasContract
from slither.core.solidity_types import ElementaryType, TypeAliasContract, Type
from slither.core.variables.state_variable import StateVariable
from slither.solc_parsing.declarations.caller_context import CallerContextExpression
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
@ -577,21 +577,26 @@ class ContractSolc(CallerContextExpression):
except (VariableNotFound, KeyError) as e:
self.log_incorrect_parsing(f"Missing state variable {e}")
def analyze_using_for(self):
def analyze_using_for(self): # pylint: disable=too-many-branches
try:
for father in self._contract.inheritance:
self._contract.using_for.update(father.using_for)
if self.is_compact_ast:
for using_for in self._usingForNotParsed:
lib_name = parse_type(using_for["libraryName"], self)
if "typeName" in using_for and using_for["typeName"]:
type_name = parse_type(using_for["typeName"], self)
else:
type_name = "*"
if type_name not in self._contract.using_for:
self._contract.using_for[type_name] = []
self._contract.using_for[type_name].append(lib_name)
if "libraryName" in using_for:
self._contract.using_for[type_name].append(
parse_type(using_for["libraryName"], self)
)
else:
# We have a list of functions. A function can be topLevel or a library function
self._analyze_function_list(using_for["functionList"], type_name)
else:
for using_for in self._usingForNotParsed:
children = using_for[self.get_children()]
@ -609,6 +614,43 @@ class ContractSolc(CallerContextExpression):
except (VariableNotFound, KeyError) as e:
self.log_incorrect_parsing(f"Missing using for {e}")
def _analyze_function_list(self, function_list: List, type_name: Type):
for f in function_list:
function_name = f["function"]["name"]
if function_name.find(".") != -1:
# Library function
self._analyze_library_function(function_name, type_name)
else:
# Top level function
for tl_function in self.compilation_unit.functions_top_level:
if tl_function.name == function_name:
self._contract.using_for[type_name].append(tl_function)
def _analyze_library_function(self, function_name: str, type_name: Type) -> None:
function_name_split = function_name.split(".")
# TODO this doesn't handle the case if there is an import with an alias
# e.g. MyImport.MyLib.a
if len(function_name_split) == 2:
library_name = function_name_split[0]
function_name = function_name_split[1]
# Get the library function
found = False
for c in self.compilation_unit.contracts:
if found:
break
if c.name == library_name:
for f in c.functions:
if f.name == function_name:
self._contract.using_for[type_name].append(f)
found = True
break
if not found:
self.log_incorrect_parsing(f"Library function not found {function_name}")
else:
self.log_incorrect_parsing(
f"Expected library function instead received {function_name}"
)
def analyze_enums(self):
try:
for father in self._contract.inheritance:

@ -311,7 +311,7 @@ class FunctionSolc(CallerContextExpression):
for node_parser in self._node_to_yulobject.values():
node_parser.analyze_expressions()
self._filter_ternary()
self._rewrite_ternary_as_if_else()
self._remove_alone_endif()
@ -1339,7 +1339,7 @@ class FunctionSolc(CallerContextExpression):
###################################################################################
###################################################################################
def _filter_ternary(self) -> bool:
def _rewrite_ternary_as_if_else(self) -> bool:
ternary_found = True
updated = False
while ternary_found:

@ -87,7 +87,7 @@ class ModifierSolc(FunctionSolc):
for node in self._node_to_nodesolc.values():
node.analyze_expressions(self)
self._filter_ternary()
self._rewrite_ternary_as_if_else()
self._remove_alone_endif()
# self._analyze_read_write()

@ -0,0 +1,153 @@
"""
Using For Top Level module
"""
import logging
from typing import TYPE_CHECKING, Dict, Union, Any
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.declarations.using_for_top_level import UsingForTopLevel
from slither.core.scope.scope import FileScope
from slither.core.solidity_types import TypeAliasTopLevel
from slither.core.declarations import (
StructureTopLevel,
EnumTopLevel,
)
from slither.solc_parsing.declarations.caller_context import CallerContextExpression
from slither.solc_parsing.solidity_types.type_parsing import parse_type
from slither.core.solidity_types.user_defined_type import UserDefinedType
if TYPE_CHECKING:
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
LOGGER = logging.getLogger("UsingForTopLevelSolc")
class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few-public-methods
"""
UsingFor class
"""
def __init__(
self,
uftl: UsingForTopLevel,
top_level_data: Dict,
slither_parser: "SlitherCompilationUnitSolc",
) -> None:
self._type_name = top_level_data["typeName"]
self._global = top_level_data["global"]
if "libraryName" in top_level_data:
self._library_name = top_level_data["libraryName"]
else:
self._functions = top_level_data["functionList"]
self._library_name = None
self._using_for = uftl
self._slither_parser = slither_parser
def analyze(self) -> None:
type_name = parse_type(self._type_name, self)
self._using_for.using_for[type_name] = []
if self._library_name is not None:
library_name = parse_type(self._library_name, self)
self._using_for.using_for[type_name].append(library_name)
self._propagate_global(type_name)
else:
for f in self._functions:
full_name_split = f["function"]["name"].split(".")
if len(full_name_split) == 1:
# Top level function
function_name = full_name_split[0]
self._analyze_top_level_function(function_name, type_name)
elif len(full_name_split) == 2:
# Library function
library_name = full_name_split[0]
function_name = full_name_split[1]
self._analyze_library_function(function_name, library_name, type_name)
else:
# probably case if there is an import with an alias we don't handle it for now
# e.g. MyImport.MyLib.a
LOGGER.warning(
f"Using for directive for function {f['function']['name']} not supported"
)
continue
def _analyze_top_level_function(
self, function_name: str, type_name: Union[TypeAliasTopLevel, UserDefinedType]
) -> None:
for tl_function in self.compilation_unit.functions_top_level:
if tl_function.name == function_name:
self._using_for.using_for[type_name].append(tl_function)
self._propagate_global(type_name)
break
def _analyze_library_function(
self,
function_name: str,
library_name: str,
type_name: Union[TypeAliasTopLevel, UserDefinedType],
) -> None:
found = False
for c in self.compilation_unit.contracts:
if found:
break
if c.name == library_name:
for cf in c.functions:
if cf.name == function_name:
self._using_for.using_for[type_name].append(cf)
self._propagate_global(type_name)
found = True
break
if not found:
LOGGER.warning(f"Library {library_name} - function {function_name} not found")
def _propagate_global(self, type_name: Union[TypeAliasTopLevel, UserDefinedType]) -> None:
if self._global:
for scope in self.compilation_unit.scopes.values():
if isinstance(type_name, TypeAliasTopLevel):
for alias in scope.user_defined_types.values():
if alias == type_name:
scope.using_for_directives.add(self._using_for)
elif isinstance(type_name, UserDefinedType):
self._propagate_global_UserDefinedType(scope, type_name)
else:
LOGGER.error(
f"Error when propagating global using for {type_name} {type(type_name)}"
)
def _propagate_global_UserDefinedType(
self, scope: Dict[Any, FileScope], type_name: UserDefinedType
):
underlying = type_name.type
if isinstance(underlying, StructureTopLevel):
for struct in scope.structures.values():
if struct == underlying:
scope.using_for_directives.add(self._using_for)
elif isinstance(underlying, EnumTopLevel):
for enum in scope.enums.values():
if enum == underlying:
scope.using_for_directives.add(self._using_for)
else:
LOGGER.error(
f"Error when propagating global {underlying} {type(underlying)} not a StructTopLevel or EnumTopLevel"
)
@property
def is_compact_ast(self) -> bool:
return self._slither_parser.is_compact_ast
@property
def compilation_unit(self) -> SlitherCompilationUnit:
return self._slither_parser.compilation_unit
def get_key(self) -> str:
return self._slither_parser.get_key()
@property
def slither_parser(self) -> "SlitherCompilationUnitSolc":
return self._slither_parser
@property
def underlying_using_for(self) -> UsingForTopLevel:
return self._using_for

@ -14,6 +14,7 @@ from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.import_directive import Import
from slither.core.declarations.pragma_directive import Pragma
from slither.core.declarations.structure_top_level import StructureTopLevel
from slither.core.declarations.using_for_top_level import UsingForTopLevel
from slither.core.scope.scope import FileScope
from slither.core.solidity_types import ElementaryType, TypeAliasTopLevel
from slither.core.variables.top_level_variable import TopLevelVariable
@ -22,6 +23,7 @@ from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc
from slither.solc_parsing.declarations.using_for_top_level import UsingForTopLevelSolc
from slither.solc_parsing.exceptions import VariableNotFound
from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc
from slither.solc_parsing.declarations.caller_context import CallerContextExpression
@ -72,6 +74,7 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
self._custom_error_parser: List[CustomErrorSolc] = []
self._variables_top_level_parser: List[TopLevelVariableSolc] = []
self._functions_top_level_parser: List[FunctionSolc] = []
self._using_for_top_level_parser: List[UsingForTopLevelSolc] = []
self._is_compact_ast = False
# self._core: SlitherCore = core
@ -226,6 +229,17 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
scope.pragmas.add(pragma)
pragma.set_offset(top_level_data["src"], self._compilation_unit)
self._compilation_unit.pragma_directives.append(pragma)
elif top_level_data[self.get_key()] == "UsingForDirective":
scope = self.compilation_unit.get_scope(filename)
usingFor = UsingForTopLevel(scope)
usingFor_parser = UsingForTopLevelSolc(usingFor, top_level_data, self)
usingFor.set_offset(top_level_data["src"], self._compilation_unit)
scope.using_for_directives.add(usingFor)
self._compilation_unit.using_for_top_level.append(usingFor)
self._using_for_top_level_parser.append(usingFor_parser)
elif top_level_data[self.get_key()] == "ImportDirective":
if self.is_compact_ast:
import_directive = Import(
@ -496,6 +510,9 @@ Please rename it, this name is reserved for Slither's internals"""
# Then we analyse state variables, functions and modifiers
self._analyze_third_part(contracts_to_be_analyzed, libraries)
[c.set_is_analyzed(False) for c in self._underlying_contract_to_parser.values()]
self._analyze_using_for(contracts_to_be_analyzed)
self._parsed = True
@ -607,6 +624,24 @@ Please rename it, this name is reserved for Slither's internals"""
else:
contracts_to_be_analyzed += [contract]
def _analyze_using_for(self, contracts_to_be_analyzed: List[ContractSolc]):
self._analyze_top_level_using_for()
while contracts_to_be_analyzed:
contract = contracts_to_be_analyzed[0]
contracts_to_be_analyzed = contracts_to_be_analyzed[1:]
all_father_analyzed = all(
self._underlying_contract_to_parser[father].is_analyzed
for father in contract.underlying_contract.inheritance
)
if not contract.underlying_contract.inheritance or all_father_analyzed:
contract.analyze_using_for()
contract.set_is_analyzed(True)
else:
contracts_to_be_analyzed += [contract]
def _analyze_enums(self, contract: ContractSolc):
# Enum must be analyzed first
contract.analyze_enums()
@ -629,7 +664,6 @@ Please rename it, this name is reserved for Slither's internals"""
# Event can refer to struct
contract.analyze_events()
contract.analyze_using_for()
contract.analyze_custom_errors()
contract.set_is_analyzed(True)
@ -653,6 +687,10 @@ Please rename it, this name is reserved for Slither's internals"""
func_parser.analyze_params()
self._compilation_unit.add_function(func_parser.underlying_function)
def _analyze_top_level_using_for(self):
for using_for in self._using_for_top_level_parser:
using_for.analyze()
def _analyze_params_custom_error(self):
for custom_error_parser in self._custom_error_parser:
custom_error_parser.analyze_params()
@ -688,6 +726,7 @@ Please rename it, this name is reserved for Slither's internals"""
for func in contract.functions + contract.modifiers:
try:
func.generate_slithir_and_analyze()
except AttributeError as e:
# This can happens for example if there is a call to an interface
# And the interface is redefined due to contract's name reuse

@ -224,6 +224,7 @@ def parse_type(
from slither.solc_parsing.variables.function_type_variable import FunctionTypeVariableSolc
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.using_for_top_level import UsingForTopLevelSolc
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
@ -259,11 +260,16 @@ def parse_type(
all_enums += enums_direct_access
contracts = sl.contracts
functions = []
elif isinstance(caller_context, (StructureTopLevelSolc, CustomErrorSolc, TopLevelVariableSolc)):
elif isinstance(
caller_context,
(StructureTopLevelSolc, CustomErrorSolc, TopLevelVariableSolc, UsingForTopLevelSolc),
):
if isinstance(caller_context, StructureTopLevelSolc):
scope = caller_context.underlying_structure.file_scope
elif isinstance(caller_context, TopLevelVariableSolc):
scope = caller_context.underlying_variable.file_scope
elif isinstance(caller_context, UsingForTopLevelSolc):
scope = caller_context.underlying_using_for.file_scope
else:
assert isinstance(caller_context, CustomErrorSolc)
custom_error = caller_context.underlying_custom_error

@ -14,7 +14,10 @@ from slither.exceptions import SlitherException
from slither.utils.colors import red
from slither.utils.output import output_to_json
from slither.tools.upgradeability.checks import all_checks
from slither.tools.upgradeability.checks.abstract_checks import AbstractCheck
from slither.tools.upgradeability.checks.abstract_checks import (
AbstractCheck,
CheckClassification,
)
from slither.tools.upgradeability.utils.command_line import (
output_detectors_json,
output_wiki,
@ -27,12 +30,14 @@ logger: logging.Logger = logging.getLogger("Slither")
logger.setLevel(logging.INFO)
def parse_args() -> argparse.Namespace:
def parse_args(check_classes: List[Type[AbstractCheck]]) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Slither Upgradeability Checks. For usage information see https://github.com/crytic/slither/wiki/Upgradeability-Checks.",
usage="slither-check-upgradeability contract.sol ContractName",
)
group_checks = parser.add_argument_group("Checks")
parser.add_argument("contract.sol", help="Codebase to analyze")
parser.add_argument("ContractName", help="Contract name (logic contract)")
@ -51,7 +56,16 @@ def parse_args() -> argparse.Namespace:
default=False,
)
parser.add_argument(
group_checks.add_argument(
"--detect",
help="Comma-separated list of detectors, defaults to all, "
f"available detectors: {', '.join(d.ARGUMENT for d in check_classes)}",
action="store",
dest="detectors_to_run",
default="all",
)
group_checks.add_argument(
"--list-detectors",
help="List available detectors",
action=ListDetectors,
@ -59,6 +73,42 @@ def parse_args() -> argparse.Namespace:
default=False,
)
group_checks.add_argument(
"--exclude",
help="Comma-separated list of detectors that should be excluded",
action="store",
dest="detectors_to_exclude",
default=None,
)
group_checks.add_argument(
"--exclude-informational",
help="Exclude informational impact analyses",
action="store_true",
default=False,
)
group_checks.add_argument(
"--exclude-low",
help="Exclude low impact analyses",
action="store_true",
default=False,
)
group_checks.add_argument(
"--exclude-medium",
help="Exclude medium impact analyses",
action="store_true",
default=False,
)
group_checks.add_argument(
"--exclude-high",
help="Exclude high impact analyses",
action="store_true",
default=False,
)
parser.add_argument(
"--markdown-root",
help="URL for markdown generation",
@ -104,6 +154,43 @@ def _get_checks() -> List[Type[AbstractCheck]]:
return detectors
def choose_checks(
args: argparse.Namespace, all_check_classes: List[Type[AbstractCheck]]
) -> List[Type[AbstractCheck]]:
detectors_to_run = []
detectors = {d.ARGUMENT: d for d in all_check_classes}
if args.detectors_to_run == "all":
detectors_to_run = all_check_classes
if args.detectors_to_exclude:
detectors_excluded = args.detectors_to_exclude.split(",")
for detector in detectors:
if detector in detectors_excluded:
detectors_to_run.remove(detectors[detector])
else:
for detector in args.detectors_to_run.split(","):
if detector in detectors:
detectors_to_run.append(detectors[detector])
else:
raise Exception(f"Error: {detector} is not a detector")
detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT)
return detectors_to_run
if args.exclude_informational:
detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != CheckClassification.INFORMATIONAL
]
if args.exclude_low:
detectors_to_run = [d for d in detectors_to_run if d.IMPACT != CheckClassification.LOW]
if args.exclude_medium:
detectors_to_run = [d for d in detectors_to_run if d.IMPACT != CheckClassification.MEDIUM]
if args.exclude_high:
detectors_to_run = [d for d in detectors_to_run if d.IMPACT != CheckClassification.HIGH]
# detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT)
return detectors_to_run
class ListDetectors(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(
self, parser: Any, *args: Any, **kwargs: Any
@ -200,11 +287,11 @@ def main() -> None:
"detectors": [],
}
args = parse_args()
detectors = _get_checks()
args = parse_args(detectors)
detectors_to_run = choose_checks(args, detectors)
v1_filename = vars(args)["contract.sol"]
number_detectors_run = 0
detectors = _get_checks()
try:
variable1 = Slither(v1_filename, **vars(args))
@ -219,7 +306,7 @@ def main() -> None:
return
v1_contract = v1_contracts[0]
detectors_results, number_detectors = _checks_on_contract(detectors, v1_contract)
detectors_results, number_detectors = _checks_on_contract(detectors_to_run, v1_contract)
json_results["detectors"] += detectors_results
number_detectors_run += number_detectors
@ -242,7 +329,7 @@ def main() -> None:
json_results["proxy-present"] = True
detectors_results, number_detectors = _checks_on_contract_and_proxy(
detectors, v1_contract, proxy_contract
detectors_to_run, v1_contract, proxy_contract
)
json_results["detectors"] += detectors_results
number_detectors_run += number_detectors
@ -267,19 +354,19 @@ def main() -> None:
if proxy_contract:
detectors_results, _ = _checks_on_contract_and_proxy(
detectors, v2_contract, proxy_contract
detectors_to_run, v2_contract, proxy_contract
)
json_results["detectors"] += detectors_results
detectors_results, number_detectors = _checks_on_contract_update(
detectors, v1_contract, v2_contract
detectors_to_run, v1_contract, v2_contract
)
json_results["detectors"] += detectors_results
number_detectors_run += number_detectors
# If there is a V2, we run the contract-only check on the V2
detectors_results, number_detectors = _checks_on_contract(detectors, v2_contract)
detectors_results, number_detectors = _checks_on_contract(detectors_to_run, v2_contract)
json_results["detectors"] += detectors_results
number_detectors_run += number_detectors

@ -23,20 +23,29 @@ from slither.all_exceptions import SlitherException
# pylint: disable=protected-access
def f_expressions(
e: AssignmentOperation, x: Union[Identifier, Literal, MemberAccess, IndexAccess]
e: Union[AssignmentOperation, BinaryOperation, TupleExpression],
x: Union[Identifier, Literal, MemberAccess, IndexAccess],
) -> None:
e._expressions.append(x)
def f_call(e, x):
def f_call(e: CallExpression, x):
e._arguments.append(x)
def f_expression(e, x):
def f_call_value(e: CallExpression, x):
e._value = x
def f_call_gas(e: CallExpression, x):
e._gas = x
def f_expression(e: Union[TypeConversion, UnaryOperation, MemberAccess], x):
e._expression = x
def f_called(e, x):
def f_called(e: CallExpression, x):
e._called = x
@ -53,13 +62,20 @@ class SplitTernaryExpression:
self.condition = None
self.copy_expression(expression, self.true_expression, self.false_expression)
def apply_copy(
def conditional_not_ahead(
self,
next_expr: Expression,
true_expression: Union[AssignmentOperation, MemberAccess],
false_expression: Union[AssignmentOperation, MemberAccess],
f: Callable,
) -> bool:
# look ahead for parenthetical expression (.. ? .. : ..)
if (
isinstance(next_expr, TupleExpression)
and len(next_expr.expressions) == 1
and isinstance(next_expr.expressions[0], ConditionalExpression)
):
next_expr = next_expr.expressions[0]
if isinstance(next_expr, ConditionalExpression):
f(true_expression, copy.copy(next_expr.then_expression))
@ -71,7 +87,6 @@ class SplitTernaryExpression:
f(false_expression, copy.copy(next_expr))
return True
# pylint: disable=too-many-branches
def copy_expression(
self, expression: Expression, true_expression: Expression, false_expression: Expression
) -> None:
@ -87,27 +102,47 @@ class SplitTernaryExpression:
):
return
# case of lib
# (.. ? .. : ..).add
if isinstance(expression, MemberAccess):
if isinstance(expression, (AssignmentOperation, BinaryOperation, TupleExpression)):
true_expression._expressions = []
false_expression._expressions = []
self.convert_expressions(expression, true_expression, false_expression)
elif isinstance(expression, CallExpression):
next_expr = expression.called
self.convert_call_expression(expression, next_expr, true_expression, false_expression)
elif isinstance(expression, (TypeConversion, UnaryOperation, MemberAccess)):
next_expr = expression.expression
if self.apply_copy(next_expr, true_expression, false_expression, f_expression):
if self.conditional_not_ahead(
next_expr, true_expression, false_expression, f_expression
):
self.copy_expression(
next_expr, true_expression.expression, false_expression.expression
expression.expression,
true_expression.expression,
false_expression.expression,
)
elif isinstance(expression, (AssignmentOperation, BinaryOperation, TupleExpression)):
true_expression._expressions = []
false_expression._expressions = []
else:
raise SlitherException(
f"Ternary operation not handled {expression}({type(expression)})"
)
def convert_expressions(
self,
expression: Union[AssignmentOperation, BinaryOperation, TupleExpression],
true_expression: Expression,
false_expression: Expression,
) -> None:
for next_expr in expression.expressions:
# TODO: can we get rid of `NoneType` expressions in `TupleExpression`?
# montyly: this might happen with unnamed tuple (ex: (,,,) = f()), but it needs to be checked
if next_expr:
if isinstance(next_expr, IndexAccess):
# create an index access for each branch
if isinstance(next_expr.expression_right, ConditionalExpression):
next_expr = _handle_ternary_access(
next_expr, true_expression, false_expression
)
if self.apply_copy(next_expr, true_expression, false_expression, f_expressions):
self.convert_index_access(next_expr, true_expression, false_expression)
if self.conditional_not_ahead(
next_expr, true_expression, false_expression, f_expressions
):
# always on last arguments added
self.copy_expression(
next_expr,
@ -115,68 +150,67 @@ class SplitTernaryExpression:
false_expression.expressions[-1],
)
elif isinstance(expression, CallExpression):
next_expr = expression.called
def convert_index_access(
self, next_expr: IndexAccess, true_expression: Expression, false_expression: Expression
) -> None:
# create an index access for each branch
# x[if cond ? 1 : 2] -> if cond { x[1] } else { x[2] }
for expr in next_expr.expressions:
if self.conditional_not_ahead(expr, true_expression, false_expression, f_expressions):
self.copy_expression(
expr,
true_expression.expressions[-1],
false_expression.expressions[-1],
)
def convert_call_expression(
self,
expression: CallExpression,
next_expr: Expression,
true_expression: Expression,
false_expression: Expression,
) -> None:
# case of lib
# (.. ? .. : ..).add
if self.apply_copy(next_expr, true_expression, false_expression, f_called):
if self.conditional_not_ahead(next_expr, true_expression, false_expression, f_called):
self.copy_expression(next_expr, true_expression.called, false_expression.called)
true_expression._arguments = []
false_expression._arguments = []
for next_expr in expression.arguments:
if self.apply_copy(next_expr, true_expression, false_expression, f_call):
# always on last arguments added
# In order to handle ternaries in both call options, gas and value, we return early if the
# conditional is not ahead to rewrite both ternaries (see `_rewrite_ternary_as_if_else`).
if expression.call_gas:
# case of (..).func{gas: .. ? .. : ..}()
next_expr = expression.call_gas
if self.conditional_not_ahead(next_expr, true_expression, false_expression, f_call_gas):
self.copy_expression(
next_expr,
true_expression.arguments[-1],
false_expression.arguments[-1],
true_expression.call_gas,
false_expression.call_gas,
)
else:
return
elif isinstance(expression, (TypeConversion, UnaryOperation)):
next_expr = expression.expression
if self.apply_copy(next_expr, true_expression, false_expression, f_expression):
if expression.call_value:
# case of (..).func{value: .. ? .. : ..}()
next_expr = expression.call_value
if self.conditional_not_ahead(
next_expr, true_expression, false_expression, f_call_value
):
self.copy_expression(
expression.expression,
true_expression.expression,
false_expression.expression,
next_expr,
true_expression.call_value,
false_expression.call_value,
)
else:
raise SlitherException(
f"Ternary operation not handled {expression}({type(expression)})"
)
return
def _handle_ternary_access(
next_expr: IndexAccess,
true_expression: AssignmentOperation,
false_expression: AssignmentOperation,
):
"""
Conditional ternary accesses are split into two accesses, one true and one false
E.g. x[if cond ? 1 : 2] -> if cond { x[1] } else { x[2] }
"""
true_index_access = IndexAccess(
next_expr.expression_left,
next_expr.expression_right.then_expression,
next_expr.type,
)
false_index_access = IndexAccess(
next_expr.expression_left,
next_expr.expression_right.else_expression,
next_expr.type,
)
true_expression._arguments = []
false_expression._arguments = []
f_expressions(
true_expression,
true_index_access,
)
f_expressions(
false_expression,
false_index_access,
for expr in expression.arguments:
if self.conditional_not_ahead(expr, true_expression, false_expression, f_call):
# always on last arguments added
self.copy_expression(
expr,
true_expression.arguments[-1],
false_expression.arguments[-1],
)
return next_expr.expression_right

@ -0,0 +1,10 @@
{
"L1": {
"a(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n",
"b(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n",
"c(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n"
},
"C": {
"libCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,10 @@
{
"L1": {
"a(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n",
"b(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n",
"c(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n"
},
"C": {
"libCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,8 @@
{
"Lib": {
"a(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n"
},
"C": {
"libCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,8 @@
{
"Lib": {
"f(St,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n"
},
"C": {
"libCall(uint16)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n"
}
}

@ -0,0 +1,10 @@
{
"L1": {
"a(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n",
"b(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n",
"c(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n"
},
"C": {
"libCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,10 @@
{
"L1": {
"a(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n",
"b(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n",
"c(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n"
},
"C": {
"topLevelCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,10 @@
{
"L1": {
"a(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n",
"b(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n",
"c(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n"
},
"C": {
"topLevelCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,10 @@
{
"L1": {
"a(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n",
"b(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n",
"c(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n"
},
"C": {
"libCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
}
}

@ -0,0 +1,11 @@
{
"C": {
"libCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n",
"topLevelCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
},
"L1": {
"a(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n",
"b(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n",
"c(Data,uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n"
}
}

@ -0,0 +1,40 @@
struct Data { mapping(uint => bool) flags; }
using L1 for Data;
function d(Data storage self, uint value) returns(bool){
return true;
}
library L1 {
function a(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
function b(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
function c(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
}
contract C {
Data knownValues;
function libCall(uint value) public {
require(knownValues.a(value));
}
}

@ -0,0 +1,40 @@
struct Data { mapping(uint => bool) flags; }
function d(Data storage self, uint value) returns(bool){
return true;
}
library L1 {
function a(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
function b(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
function c(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
}
contract C {
using L1 for Data;
Data knownValues;
function libCall(uint value) public {
require(knownValues.a(value));
}
}

@ -0,0 +1,27 @@
using {a} for Data;
struct Data { mapping(uint => bool) flags; }
function a(Data storage self, uint value, uint value2) returns(bool){
return false;
}
library Lib {
function a(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
}
contract C {
using Lib for Data;
Data knownValues;
function libCall(uint value) public {
require(knownValues.a(value));
}
}

@ -0,0 +1,25 @@
using {f} for St;
struct St { uint field; }
function f(St storage self, uint8 v) view returns(uint){
return 0;
}
library Lib {
function f(St storage self, uint256 v) public view returns (uint) {
return 1;
}
}
contract C {
using Lib for St;
St st;
function libCall(uint16 v) public view returns(uint){
return st.f(v); // return 1
}
}

@ -0,0 +1,37 @@
struct Data { mapping(uint => bool) flags; }
library L1 {
function a(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
function b(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
function c(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
}
contract C {
using {L1.a, L1.b} for Data;
Data knownValues;
function libCall(uint value) public {
require(knownValues.a(value));
}
}

@ -0,0 +1,41 @@
struct Data { mapping(uint => bool) flags; }
function d(Data storage self, uint value) returns(bool){
return true;
}
library L1 {
function a(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
function b(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
function c(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
}
contract C {
using {L1.a, L1.b, d} for Data;
Data knownValues;
function topLevelCall(uint value) public {
require(knownValues.d(value));
}
}

@ -0,0 +1,41 @@
struct Data { mapping(uint => bool) flags; }
using {L1.a, L1.b, d} for Data;
function d(Data storage self, uint value) returns(bool){
return true;
}
library L1 {
function a(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
function b(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
function c(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
}
contract C {
Data knownValues;
function topLevelCall(uint value) public {
require(knownValues.d(value));
}
}

@ -0,0 +1,40 @@
struct Data { mapping(uint => bool) flags; }
using {L1.a, L1.b, d} for Data;
function d(Data storage self, uint value) returns(bool){
return true;
}
library L1 {
function a(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
function b(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
function c(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
}
contract C {
Data knownValues;
function libCall(uint value) public {
require(knownValues.a(value));
}
}

@ -0,0 +1,15 @@
import "./using-for-library-0.8.0.sol";
contract C {
Data knownValues;
function libCall(uint value) public {
require(knownValues.a(value));
}
function topLevelCall(uint value) public {
require(knownValues.d(value));
}
}

@ -0,0 +1,32 @@
struct Data { mapping(uint => bool) flags; }
using L1 for Data global;
using {d} for Data global;
function d(Data storage self, uint value) returns(bool){
return true;
}
library L1 {
function a(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
function b(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
function c(Data storage self, uint value) public
view
returns (bool)
{
return true;
}
}

@ -1,3 +1,7 @@
interface Test {
function test() external payable returns (uint);
function testTuple() external payable returns (uint, uint);
}
contract C {
// TODO
// 1) support variable declarations
@ -21,4 +25,18 @@ contract C {
function d(bool cond, bytes calldata x) external {
bytes1 a = x[cond ? 1 : 2];
}
function e(address one, address two) public {
uint x = Test(one).test{value: msg.sender == two ? 1 : 2, gas: true ? 2 : gasleft()}();
}
// Parenthetical expression
function f(address one, address two) public {
uint x = Test(one).test{value: msg.sender == two ? 1 : 2, gas: true ? (1 == 1 ? 1 : 2) : gasleft()}();
}
// Unused tuple variable
function g(address one) public {
(, uint x) = Test(one).testTuple();
}
}

@ -9,10 +9,10 @@ def test_ternary_conversions() -> None:
slither = Slither("./tests/slithir/ternary_expressions.sol")
for contract in slither.contracts:
for function in contract.functions:
for node in function.nodes:
if node.type in [NodeType.IF, NodeType.IFLOOP]:
vars_declared = 0
vars_assigned = 0
for node in function.nodes:
if node.type in [NodeType.IF, NodeType.IFLOOP]:
# Iterate over true and false son
for inner_node in node.sons:

@ -424,6 +424,15 @@ ALL_TESTS = [
Test("free_functions/new_operator.sol", ["0.8.12"]),
Test("free_functions/library_constant_function_collision.sol", ["0.8.12"]),
Test("ternary-with-max.sol", ["0.8.15"]),
Test("using-for-1-0.8.0.sol", ["0.8.15"]),
Test("using-for-2-0.8.0.sol", ["0.8.15"]),
Test("using-for-3-0.8.0.sol", ["0.8.15"]),
Test("using-for-4-0.8.0.sol", ["0.8.15"]),
Test("using-for-functions-list-1-0.8.0.sol", ["0.8.15"]),
Test("using-for-functions-list-2-0.8.0.sol", ["0.8.15"]),
Test("using-for-functions-list-3-0.8.0.sol", ["0.8.15"]),
Test("using-for-functions-list-4-0.8.0.sol", ["0.8.15"]),
Test("using-for-global-0.8.0.sol", ["0.8.15"]),
Test("library_event-0.8.16.sol", ["0.8.16"]),
Test("top-level-struct-0.8.0.sol", ["0.8.0"]),
]

@ -7,6 +7,7 @@ from solc_select import solc_select
from slither import Slither
from slither.detectors import all_detectors
from slither.detectors.abstract_detector import AbstractDetector
from slither.slithir.operations import LibraryCall
def _run_all_detectors(slither: Slither) -> None:
@ -69,3 +70,25 @@ def test_upgradeable_comments() -> None:
v1 = compilation_unit.get_contract_from_name("V1")[0]
assert v0.is_upgradeable
assert v1.upgradeable_version == "version_1"
def test_using_for_top_level_same_name() -> None:
solc_select.switch_global_version("0.8.15", always_install=True)
slither = Slither("./tests/ast-parsing/using-for-3-0.8.0.sol")
contract_c = slither.get_contract_from_name("C")[0]
libCall = contract_c.get_function_from_full_name("libCall(uint256)")
for ir in libCall.all_slithir_operations():
if isinstance(ir, LibraryCall) and ir.destination == "Lib" and ir.function_name == "a":
return
assert False
def test_using_for_top_level_implicit_conversion() -> None:
solc_select.switch_global_version("0.8.15", always_install=True)
slither = Slither("./tests/ast-parsing/using-for-4-0.8.0.sol")
contract_c = slither.get_contract_from_name("C")[0]
libCall = contract_c.get_function_from_full_name("libCall(uint16)")
for ir in libCall.all_slithir_operations():
if isinstance(ir, LibraryCall) and ir.destination == "Lib" and ir.function_name == "f":
return
assert False

Loading…
Cancel
Save