Merge pull request #1563 from crytic/fix-usingfor

Improve support using for with aliasing
pull/1568/head
Feist Josselin 2 years ago committed by GitHub
commit a9ee1371ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 78
      slither/solc_parsing/declarations/contract.py
  2. 54
      slither/solc_parsing/declarations/using_for_top_level.py
  3. BIN
      tests/ast-parsing/compile/using-for-alias-contract-0.8.0.sol-0.8.15-compact.zip
  4. BIN
      tests/ast-parsing/compile/using-for-alias-top-level-0.8.0.sol-0.8.15-compact.zip
  5. 9
      tests/ast-parsing/expected/using-for-alias-contract-0.8.0.sol-0.8.15-compact.json
  6. 9
      tests/ast-parsing/expected/using-for-alias-top-level-0.8.0.sol-0.8.15-compact.json
  7. 14
      tests/ast-parsing/using-for-alias-contract-0.8.0.sol
  8. 11
      tests/ast-parsing/using-for-alias-dep1.sol
  9. 9
      tests/ast-parsing/using-for-alias-dep2.sol
  10. 15
      tests/ast-parsing/using-for-alias-top-level-0.8.0.sol
  11. 2
      tests/test_ast_parsing.py
  12. 38
      tests/test_features.py

@ -616,39 +616,55 @@ class ContractSolc(CallerContextExpression):
def _analyze_function_list(self, function_list: List, type_name: Type): def _analyze_function_list(self, function_list: List, type_name: Type):
for f in function_list: for f in function_list:
function_name = f["function"]["name"] full_name_split = f["function"]["name"].split(".")
if function_name.find(".") != -1: if len(full_name_split) == 1:
# Library function
self._analyze_library_function(function_name, type_name)
else:
# Top level function # Top level function
for tl_function in self.compilation_unit.functions_top_level: function_name = full_name_split[0]
if tl_function.name == function_name: self._analyze_top_level_function(function_name, type_name)
self._contract.using_for[type_name].append(tl_function) elif len(full_name_split) == 2:
# It can be a top level function behind an aliased import
def _analyze_library_function(self, function_name: str, type_name: Type) -> None: # or a library function
function_name_split = function_name.split(".") first_part = full_name_split[0]
# TODO this doesn't handle the case if there is an import with an alias function_name = full_name_split[1]
# e.g. MyImport.MyLib.a self._check_aliased_import(first_part, function_name, type_name)
if len(function_name_split) == 2: else:
library_name = function_name_split[0] # MyImport.MyLib.a we don't care of the alias
function_name = function_name_split[1] library_name = full_name_split[1]
# Get the library function function_name = full_name_split[2]
found = False self._analyze_library_function(library_name, function_name, type_name)
for c in self.compilation_unit.contracts:
if found: def _check_aliased_import(self, first_part: str, function_name: str, type_name: Type):
break # We check if the first part appear as alias for an import
if c.name == library_name: # if it is then function_name must be a top level function
for f in c.functions: # otherwise it's a library function
if f.name == function_name: for i in self._contract.file_scope.imports:
self._contract.using_for[type_name].append(f) if i.alias == first_part:
found = True self._analyze_top_level_function(function_name, type_name)
break return
if not found: self._analyze_library_function(first_part, function_name, type_name)
self.log_incorrect_parsing(f"Library function not found {function_name}")
else: 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( 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): def analyze_enums(self):

@ -2,19 +2,19 @@
Using For Top Level module Using For Top Level module
""" """
import logging 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.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 ( from slither.core.declarations import (
StructureTopLevel, StructureTopLevel,
EnumTopLevel, 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.declarations.caller_context import CallerContextExpression
from slither.solc_parsing.solidity_types.type_parsing import parse_type from slither.solc_parsing.solidity_types.type_parsing import parse_type
from slither.core.solidity_types.user_defined_type import UserDefinedType
if TYPE_CHECKING: if TYPE_CHECKING:
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
@ -58,20 +58,34 @@ class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few-
full_name_split = f["function"]["name"].split(".") full_name_split = f["function"]["name"].split(".")
if len(full_name_split) == 1: if len(full_name_split) == 1:
# Top level function # 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) self._analyze_top_level_function(function_name, type_name)
elif len(full_name_split) == 2: elif len(full_name_split) == 2:
# Library function # It can be a top level function behind an aliased import
library_name = full_name_split[0] # or a library function
first_part = full_name_split[0]
function_name = full_name_split[1] 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: else:
# probably case if there is an import with an alias we don't handle it for now # MyImport.MyLib.a we don't care of the alias
# e.g. MyImport.MyLib.a library_name_str = full_name_split[1]
LOGGER.warning( function_name = full_name_split[2]
f"Using for directive for function {f['function']['name']} not supported" self._analyze_library_function(library_name_str, function_name, type_name)
)
continue 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( def _analyze_top_level_function(
self, function_name: str, type_name: Union[TypeAliasTopLevel, UserDefinedType] self, function_name: str, type_name: Union[TypeAliasTopLevel, UserDefinedType]
@ -84,8 +98,8 @@ class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few-
def _analyze_library_function( def _analyze_library_function(
self, self,
function_name: str,
library_name: str, library_name: str,
function_name: str,
type_name: Union[TypeAliasTopLevel, UserDefinedType], type_name: Union[TypeAliasTopLevel, UserDefinedType],
) -> None: ) -> None:
found = False found = False
@ -100,7 +114,9 @@ class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few-
found = True found = True
break break
if not found: 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: def _propagate_global(self, type_name: Union[TypeAliasTopLevel, UserDefinedType]) -> None:
if self._global: 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)}" f"Error when propagating global using for {type_name} {type(type_name)}"
) )
def _propagate_global_UserDefinedType( def _propagate_global_UserDefinedType(self, scope: FileScope, type_name: UserDefinedType):
self, scope: Dict[Any, FileScope], type_name: UserDefinedType
):
underlying = type_name.type underlying = type_name.type
if isinstance(underlying, StructureTopLevel): if isinstance(underlying, StructureTopLevel):
for struct in scope.structures.values(): for struct in scope.structures.values():

@ -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"
}
}

@ -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"
}
}

@ -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();
}
}

@ -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;
}
}

@ -0,0 +1,9 @@
function a(uint256 value) returns(bool) {
return true;
}
library Lib {
function b(uint256 value) public returns(bool) {
return true;
}
}

@ -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();
}
}

@ -428,6 +428,8 @@ ALL_TESTS = [
Test("using-for-2-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-3-0.8.0.sol", ["0.8.15"]),
Test("using-for-4-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-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-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-3-0.8.0.sol", ["0.8.15"]),

@ -7,7 +7,7 @@ from solc_select import solc_select
from slither import Slither from slither import Slither
from slither.detectors import all_detectors from slither.detectors import all_detectors
from slither.detectors.abstract_detector import AbstractDetector 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: 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": if isinstance(ir, LibraryCall) and ir.destination == "Lib" and ir.function_name == "f":
return return
assert False 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

Loading…
Cancel
Save