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. 46
      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,23 +616,41 @@ 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
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, 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]
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:
@ -645,10 +663,8 @@ class ContractSolc(CallerContextExpression):
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}"
f"Contract level using for: Library {library_name} - function {function_name} not found"
)
def analyze_enums(self):

@ -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
@ -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():

@ -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-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"]),

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

Loading…
Cancel
Save