diff --git a/slither/core/compilation_unit.py b/slither/core/compilation_unit.py index a2568a5de4..d97b7fbf5e 100644 --- a/slither/core/compilation_unit.py +++ b/slither/core/compilation_unit.py @@ -16,6 +16,7 @@ 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 @@ def __init__(self, core: "SlitherCore", crytic_compilation_unit: CompilationUnit 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 @@ def variables_top_level(self) -> List[TopLevelVariable]: 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 diff --git a/slither/core/declarations/__init__.py b/slither/core/declarations/__init__.py index 3b619c1d15..f891ad6210 100644 --- a/slither/core/declarations/__init__.py +++ b/slither/core/declarations/__init__.py @@ -12,5 +12,8 @@ ) 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 diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index a90e2591eb..e0191ac2ef 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -79,6 +79,7 @@ def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope # 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 @@ def events_as_dict(self) -> Dict[str, "Event"]: 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 ################################################################################### ################################################################################### diff --git a/slither/core/declarations/using_for_top_level.py b/slither/core/declarations/using_for_top_level.py new file mode 100644 index 0000000000..a1b43e1c16 --- /dev/null +++ b/slither/core/declarations/using_for_top_level.py @@ -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 diff --git a/slither/core/scope/scope.py b/slither/core/scope/scope.py index 2d1c114910..1eb344c2be 100644 --- a/slither/core/scope/scope.py +++ b/slither/core/scope/scope.py @@ -8,6 +8,7 @@ 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 @@ def __init__(self, filename: Filename): # 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 @@ def add_accesible_scopes(self) -> bool: 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 diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 0b43184bd6..0d2ef1b741 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -15,6 +15,7 @@ ) 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,10 +903,14 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]): # pylint: dis lib_contract_type.type, Contract ): continue - lib_contract = lib_contract_type.type - for lib_func in lib_contract.functions: - if lib_func.name == ins.ori.variable_right: - candidates.append(lib_func) + 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: + candidates.append(lib_func) if len(candidates) == 1: lib_func = candidates[0] @@ -1326,9 +1339,32 @@ 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]: - lib_contract = contract.file_scope.get_contract_from_name(str(destination)) + 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( lib_contract, @@ -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: diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index c509258e98..3095b6854b 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -6,7 +6,7 @@ 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 @@ def analyze_state_variables(self): 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 @@ def analyze_using_for(self): 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: diff --git a/slither/solc_parsing/declarations/using_for_top_level.py b/slither/solc_parsing/declarations/using_for_top_level.py new file mode 100644 index 0000000000..3ec191d463 --- /dev/null +++ b/slither/solc_parsing/declarations/using_for_top_level.py @@ -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 diff --git a/slither/solc_parsing/slither_compilation_unit_solc.py b/slither/solc_parsing/slither_compilation_unit_solc.py index 6dce9b0055..7bae88c5cc 100644 --- a/slither/solc_parsing/slither_compilation_unit_solc.py +++ b/slither/solc_parsing/slither_compilation_unit_solc.py @@ -14,6 +14,7 @@ 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.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 @@ def __init__(self, compilation_unit: SlitherCompilationUnit): 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 @@ def parse_top_level_from_loaded_json( 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 @@ def parse_contracts(self): # pylint: disable=too-many-statements,too-many-branc # 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 @@ def _analyze_third_part( 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 @@ def _analyze_struct_events(self, contract: ContractSolc): # 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 @@ def _analyze_params_top_level_function(self): 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 @@ def _convert_to_slithir(self): 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 diff --git a/slither/solc_parsing/solidity_types/type_parsing.py b/slither/solc_parsing/solidity_types/type_parsing.py index 9a8ef5db20..b62f908d1f 100644 --- a/slither/solc_parsing/solidity_types/type_parsing.py +++ b/slither/solc_parsing/solidity_types/type_parsing.py @@ -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 diff --git a/tests/ast-parsing/compile/using-for-1-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-1-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 0000000000..6505c61e0b Binary files /dev/null and b/tests/ast-parsing/compile/using-for-1-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-2-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-2-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 0000000000..7c6a280204 Binary files /dev/null and b/tests/ast-parsing/compile/using-for-2-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-3-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-3-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 0000000000..e51c777db4 Binary files /dev/null and b/tests/ast-parsing/compile/using-for-3-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-4-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-4-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 0000000000..12452d862d Binary files /dev/null and b/tests/ast-parsing/compile/using-for-4-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-functions-list-1-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-functions-list-1-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 0000000000..6a6064a153 Binary files /dev/null and b/tests/ast-parsing/compile/using-for-functions-list-1-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-functions-list-2-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-functions-list-2-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 0000000000..33ce991462 Binary files /dev/null and b/tests/ast-parsing/compile/using-for-functions-list-2-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-functions-list-3-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-functions-list-3-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 0000000000..3944ab586f Binary files /dev/null and b/tests/ast-parsing/compile/using-for-functions-list-3-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-functions-list-4-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-functions-list-4-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 0000000000..8889d83fb1 Binary files /dev/null and b/tests/ast-parsing/compile/using-for-functions-list-4-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-global-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-global-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 0000000000..baef956059 Binary files /dev/null and b/tests/ast-parsing/compile/using-for-global-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/expected/using-for-1-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-1-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 0000000000..01536efb97 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-1-0.8.0.sol-0.8.15-compact.json @@ -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" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-2-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-2-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 0000000000..01536efb97 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-2-0.8.0.sol-0.8.15-compact.json @@ -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" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-3-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-3-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 0000000000..a3b8987d4d --- /dev/null +++ b/tests/ast-parsing/expected/using-for-3-0.8.0.sol-0.8.15-compact.json @@ -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" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-4-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-4-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 0000000000..6ff9c97801 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-4-0.8.0.sol-0.8.15-compact.json @@ -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" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-functions-list-1-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-functions-list-1-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 0000000000..01536efb97 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-functions-list-1-0.8.0.sol-0.8.15-compact.json @@ -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" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-functions-list-2-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-functions-list-2-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 0000000000..a41d2ba182 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-functions-list-2-0.8.0.sol-0.8.15-compact.json @@ -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" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-functions-list-3-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-functions-list-3-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 0000000000..a41d2ba182 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-functions-list-3-0.8.0.sol-0.8.15-compact.json @@ -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" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-functions-list-4-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-functions-list-4-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 0000000000..01536efb97 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-functions-list-4-0.8.0.sol-0.8.15-compact.json @@ -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" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/expected/using-for-global-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-global-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 0000000000..f2119ecf13 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-global-0.8.0.sol-0.8.15-compact.json @@ -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" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/using-for-1-0.8.0.sol b/tests/ast-parsing/using-for-1-0.8.0.sol new file mode 100644 index 0000000000..201e18527e --- /dev/null +++ b/tests/ast-parsing/using-for-1-0.8.0.sol @@ -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)); + } + +} diff --git a/tests/ast-parsing/using-for-2-0.8.0.sol b/tests/ast-parsing/using-for-2-0.8.0.sol new file mode 100644 index 0000000000..cb0bb8ba1b --- /dev/null +++ b/tests/ast-parsing/using-for-2-0.8.0.sol @@ -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)); + } + +} diff --git a/tests/ast-parsing/using-for-3-0.8.0.sol b/tests/ast-parsing/using-for-3-0.8.0.sol new file mode 100644 index 0000000000..1da4f3dc65 --- /dev/null +++ b/tests/ast-parsing/using-for-3-0.8.0.sol @@ -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)); + } + +} \ No newline at end of file diff --git a/tests/ast-parsing/using-for-4-0.8.0.sol b/tests/ast-parsing/using-for-4-0.8.0.sol new file mode 100644 index 0000000000..d50e107a4d --- /dev/null +++ b/tests/ast-parsing/using-for-4-0.8.0.sol @@ -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 + } + +} \ No newline at end of file diff --git a/tests/ast-parsing/using-for-functions-list-1-0.8.0.sol b/tests/ast-parsing/using-for-functions-list-1-0.8.0.sol new file mode 100644 index 0000000000..52ed5087ed --- /dev/null +++ b/tests/ast-parsing/using-for-functions-list-1-0.8.0.sol @@ -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)); + } + +} diff --git a/tests/ast-parsing/using-for-functions-list-2-0.8.0.sol b/tests/ast-parsing/using-for-functions-list-2-0.8.0.sol new file mode 100644 index 0000000000..d41163dee7 --- /dev/null +++ b/tests/ast-parsing/using-for-functions-list-2-0.8.0.sol @@ -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)); + } + +} diff --git a/tests/ast-parsing/using-for-functions-list-3-0.8.0.sol b/tests/ast-parsing/using-for-functions-list-3-0.8.0.sol new file mode 100644 index 0000000000..7e5db57767 --- /dev/null +++ b/tests/ast-parsing/using-for-functions-list-3-0.8.0.sol @@ -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)); + } + +} diff --git a/tests/ast-parsing/using-for-functions-list-4-0.8.0.sol b/tests/ast-parsing/using-for-functions-list-4-0.8.0.sol new file mode 100644 index 0000000000..ecce5e7643 --- /dev/null +++ b/tests/ast-parsing/using-for-functions-list-4-0.8.0.sol @@ -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)); + } + +} diff --git a/tests/ast-parsing/using-for-global-0.8.0.sol b/tests/ast-parsing/using-for-global-0.8.0.sol new file mode 100644 index 0000000000..c5b112259d --- /dev/null +++ b/tests/ast-parsing/using-for-global-0.8.0.sol @@ -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)); + } + + +} diff --git a/tests/ast-parsing/using-for-library-0.8.0.sol b/tests/ast-parsing/using-for-library-0.8.0.sol new file mode 100644 index 0000000000..97ee39ca66 --- /dev/null +++ b/tests/ast-parsing/using-for-library-0.8.0.sol @@ -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; + } + +} diff --git a/tests/test_ast_parsing.py b/tests/test_ast_parsing.py index 47f230534f..96aad1a634 100644 --- a/tests/test_ast_parsing.py +++ b/tests/test_ast_parsing.py @@ -424,6 +424,15 @@ def make_version(minor: int, patch_min: int, patch_max: int) -> List[str]: 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"]), ] diff --git a/tests/test_features.py b/tests/test_features.py index 4b4564b504..e6e781881d 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -7,6 +7,7 @@ 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