diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index 3095b6854..a93914449 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -616,39 +616,55 @@ class ContractSolc(CallerContextExpression): 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: + full_name_split = f["function"]["name"].split(".") + if len(full_name_split) == 1: # 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: + function_name = full_name_split[0] + self._analyze_top_level_function(function_name, type_name) + elif len(full_name_split) == 2: + # It can be a top level function behind an aliased import + # or a library function + first_part = full_name_split[0] + function_name = full_name_split[1] + self._check_aliased_import(first_part, function_name, type_name) + else: + # MyImport.MyLib.a we don't care of the alias + library_name = full_name_split[1] + function_name = full_name_split[2] + self._analyze_library_function(library_name, function_name, type_name) + + def _check_aliased_import(self, first_part: str, function_name: str, type_name: Type): + # We check if the first part appear as alias for an import + # if it is then function_name must be a top level function + # otherwise it's a library function + for i in self._contract.file_scope.imports: + if i.alias == first_part: + self._analyze_top_level_function(function_name, type_name) + return + self._analyze_library_function(first_part, function_name, type_name) + + def _analyze_top_level_function(self, function_name: str, type_name: Type): + 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, library_name: str, function_name: str, type_name: Type + ) -> None: + # 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"Expected library function instead received {function_name}" + f"Contract level using for: Library {library_name} - function {function_name} not found" ) def analyze_enums(self): diff --git a/slither/solc_parsing/declarations/using_for_top_level.py b/slither/solc_parsing/declarations/using_for_top_level.py index 3ec191d46..4037a52a3 100644 --- a/slither/solc_parsing/declarations/using_for_top_level.py +++ b/slither/solc_parsing/declarations/using_for_top_level.py @@ -58,20 +58,34 @@ class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few- full_name_split = f["function"]["name"].split(".") if len(full_name_split) == 1: # Top level function - function_name = full_name_split[0] + function_name: str = 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] + # It can be a top level function behind an aliased import + # or a library function + first_part = full_name_split[0] function_name = full_name_split[1] - self._analyze_library_function(function_name, library_name, type_name) + self._check_aliased_import(first_part, function_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 + # MyImport.MyLib.a we don't care of the alias + library_name_str = full_name_split[1] + function_name = full_name_split[2] + self._analyze_library_function(library_name_str, function_name, type_name) + + def _check_aliased_import( + self, + first_part: str, + function_name: str, + type_name: Union[TypeAliasTopLevel, UserDefinedType], + ): + # We check if the first part appear as alias for an import + # if it is then function_name must be a top level function + # otherwise it's a library function + for i in self._using_for.file_scope.imports: + if i.alias == first_part: + self._analyze_top_level_function(function_name, type_name) + return + self._analyze_library_function(first_part, function_name, type_name) def _analyze_top_level_function( self, function_name: str, type_name: Union[TypeAliasTopLevel, UserDefinedType] @@ -84,8 +98,8 @@ class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few- def _analyze_library_function( self, - function_name: str, library_name: str, + function_name: str, type_name: Union[TypeAliasTopLevel, UserDefinedType], ) -> None: found = False @@ -100,7 +114,9 @@ class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few- found = True break if not found: - LOGGER.warning(f"Library {library_name} - function {function_name} not found") + LOGGER.warning( + f"Top level using for: Library {library_name} - function {function_name} not found" + ) def _propagate_global(self, type_name: Union[TypeAliasTopLevel, UserDefinedType]) -> None: if self._global: @@ -116,9 +132,7 @@ class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few- f"Error when propagating global using for {type_name} {type(type_name)}" ) - def _propagate_global_UserDefinedType( - self, scope: Dict[Any, FileScope], type_name: UserDefinedType - ): + def _propagate_global_UserDefinedType(self, scope: FileScope, type_name: UserDefinedType): underlying = type_name.type if isinstance(underlying, StructureTopLevel): for struct in scope.structures.values(): diff --git a/tests/ast-parsing/compile/using-for-alias-contract-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-alias-contract-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 000000000..c460e057f Binary files /dev/null and b/tests/ast-parsing/compile/using-for-alias-contract-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/compile/using-for-alias-top-level-0.8.0.sol-0.8.15-compact.zip b/tests/ast-parsing/compile/using-for-alias-top-level-0.8.0.sol-0.8.15-compact.zip new file mode 100644 index 000000000..4a0cfea17 Binary files /dev/null and b/tests/ast-parsing/compile/using-for-alias-top-level-0.8.0.sol-0.8.15-compact.zip differ diff --git a/tests/ast-parsing/expected/using-for-alias-contract-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-alias-contract-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 000000000..809fcb486 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-alias-contract-0.8.0.sol-0.8.15-compact.json @@ -0,0 +1,9 @@ +{ + "C": { + "topLevel(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n", + "libCall(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + }, + "Lib": { + "b(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/expected/using-for-alias-top-level-0.8.0.sol-0.8.15-compact.json b/tests/ast-parsing/expected/using-for-alias-top-level-0.8.0.sol-0.8.15-compact.json new file mode 100644 index 000000000..d16a34808 --- /dev/null +++ b/tests/ast-parsing/expected/using-for-alias-top-level-0.8.0.sol-0.8.15-compact.json @@ -0,0 +1,9 @@ +{ + "Lib": { + "b(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n" + }, + "C": { + "topLevel(uint256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n", + "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/using-for-alias-contract-0.8.0.sol b/tests/ast-parsing/using-for-alias-contract-0.8.0.sol new file mode 100644 index 000000000..d6906d5ab --- /dev/null +++ b/tests/ast-parsing/using-for-alias-contract-0.8.0.sol @@ -0,0 +1,14 @@ +import "./using-for-alias-dep1.sol"; + +contract C { + using {T3.a, T3.Lib.b} for uint256; + + function topLevel(uint256 value) public { + value.a(); + } + + function libCall(uint256 value) public { + value.b(); + } + +} diff --git a/tests/ast-parsing/using-for-alias-dep1.sol b/tests/ast-parsing/using-for-alias-dep1.sol new file mode 100644 index 000000000..db28e4a71 --- /dev/null +++ b/tests/ast-parsing/using-for-alias-dep1.sol @@ -0,0 +1,11 @@ +import "./using-for-alias-dep2.sol" as T3; + +function b(uint256 value) returns(bool) { + return true; +} + +library Lib { + function a(uint256 value) public returns(bool) { + return true; + } +} diff --git a/tests/ast-parsing/using-for-alias-dep2.sol b/tests/ast-parsing/using-for-alias-dep2.sol new file mode 100644 index 000000000..17ff96452 --- /dev/null +++ b/tests/ast-parsing/using-for-alias-dep2.sol @@ -0,0 +1,9 @@ +function a(uint256 value) returns(bool) { + return true; +} + +library Lib { + function b(uint256 value) public returns(bool) { + return true; + } +} \ No newline at end of file diff --git a/tests/ast-parsing/using-for-alias-top-level-0.8.0.sol b/tests/ast-parsing/using-for-alias-top-level-0.8.0.sol new file mode 100644 index 000000000..ed7e22bac --- /dev/null +++ b/tests/ast-parsing/using-for-alias-top-level-0.8.0.sol @@ -0,0 +1,15 @@ +import "./using-for-alias-dep1.sol"; + +using {T3.a, T3.Lib.b} for uint256; + +contract C { + + function topLevel(uint256 value) public { + value.a(); + } + + function libCall(uint256 value) public { + value.b(); + } + +} diff --git a/tests/test_ast_parsing.py b/tests/test_ast_parsing.py index 96aad1a63..04927b7f4 100644 --- a/tests/test_ast_parsing.py +++ b/tests/test_ast_parsing.py @@ -428,6 +428,8 @@ ALL_TESTS = [ 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-alias-contract-0.8.0.sol", ["0.8.15"]), + Test("using-for-alias-top-level-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"]), diff --git a/tests/test_features.py b/tests/test_features.py index e6e781881..a5541b589 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -7,7 +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 +from slither.slithir.operations import LibraryCall, InternalCall def _run_all_detectors(slither: Slither) -> None: @@ -92,3 +92,39 @@ def test_using_for_top_level_implicit_conversion() -> None: if isinstance(ir, LibraryCall) and ir.destination == "Lib" and ir.function_name == "f": return assert False + + +def test_using_for_alias_top_level() -> None: + solc_select.switch_global_version("0.8.15", always_install=True) + slither = Slither("./tests/ast-parsing/using-for-alias-top-level-0.8.0.sol") + contract_c = slither.get_contract_from_name("C")[0] + libCall = contract_c.get_function_from_full_name("libCall(uint256)") + ok = False + for ir in libCall.all_slithir_operations(): + if isinstance(ir, LibraryCall) and ir.destination == "Lib" and ir.function_name == "b": + ok = True + if not ok: + assert False + topLevelCall = contract_c.get_function_from_full_name("topLevel(uint256)") + for ir in topLevelCall.all_slithir_operations(): + if isinstance(ir, InternalCall) and ir.function_name == "a": + return + assert False + + +def test_using_for_alias_contract() -> None: + solc_select.switch_global_version("0.8.15", always_install=True) + slither = Slither("./tests/ast-parsing/using-for-alias-contract-0.8.0.sol") + contract_c = slither.get_contract_from_name("C")[0] + libCall = contract_c.get_function_from_full_name("libCall(uint256)") + ok = False + for ir in libCall.all_slithir_operations(): + if isinstance(ir, LibraryCall) and ir.destination == "Lib" and ir.function_name == "b": + ok = True + if not ok: + assert False + topLevelCall = contract_c.get_function_from_full_name("topLevel(uint256)") + for ir in topLevelCall.all_slithir_operations(): + if isinstance(ir, InternalCall) and ir.function_name == "a": + return + assert False