Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve support using for with aliasing #1563

Merged
merged 4 commits into from
Jan 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 47 additions & 31 deletions slither/solc_parsing/declarations/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,39 +616,55 @@ def analyze_using_for(self): # pylint: disable=too-many-branches

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):
Expand Down
54 changes: 34 additions & 20 deletions slither/solc_parsing/declarations/using_for_top_level.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@
Using For Top Level module
"""
import logging
from typing import TYPE_CHECKING, Dict, Union, Any
from typing import TYPE_CHECKING, Dict, Union

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.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.solidity_types.user_defined_type import UserDefinedType
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
Expand Down Expand Up @@ -58,20 +58,34 @@ def analyze(self) -> None:
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]
Expand All @@ -84,8 +98,8 @@ def _analyze_top_level_function(

def _analyze_library_function(
self,
function_name: str,
library_name: str,
function_name: str,
type_name: Union[TypeAliasTopLevel, UserDefinedType],
) -> None:
found = False
Expand All @@ -100,7 +114,9 @@ def _analyze_library_function(
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:
Expand All @@ -116,9 +132,7 @@ def _propagate_global(self, type_name: Union[TypeAliasTopLevel, UserDefinedType]
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():
Expand Down
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -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"
}
}
14 changes: 14 additions & 0 deletions tests/ast-parsing/using-for-alias-contract-0.8.0.sol
Original file line number Diff line number Diff line change
@@ -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();
}

}
11 changes: 11 additions & 0 deletions tests/ast-parsing/using-for-alias-dep1.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
9 changes: 9 additions & 0 deletions tests/ast-parsing/using-for-alias-dep2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function a(uint256 value) returns(bool) {
return true;
}

library Lib {
function b(uint256 value) public returns(bool) {
return true;
}
}
15 changes: 15 additions & 0 deletions tests/ast-parsing/using-for-alias-top-level-0.8.0.sol
Original file line number Diff line number Diff line change
@@ -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();
}

}
2 changes: 2 additions & 0 deletions tests/test_ast_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,8 @@ def make_version(minor: int, patch_min: int, patch_max: int) -> List[str]:
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"]),
Expand Down
38 changes: 37 additions & 1 deletion tests/test_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +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
from slither.slithir.operations import LibraryCall, InternalCall


def _run_all_detectors(slither: Slither) -> None:
Expand Down Expand Up @@ -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