Improve top-level function suppoort and import with renaming

- Standardize Import object to ahve have the absolute path
  - Allow node in top level function to look for elements in other
location (fix https://github.com/crytic/slither/issues/911)
  - Improve support for file import with renaming that are used to call
top-level function
    - SolidityImportPlaceHolder inherits now directly from Variable,
and has a proper lookup on the IR generation
  - Active all the top-level tests that were failing and disabled in the
CI
  - Extract 'find_variable' related functions from the
epressions_parsing module to its own module (the file was becomming too
large)
  - Update tests to use 1bd1d41e52
pull/945/head
Josselin 3 years ago
parent 903799c02f
commit f4e86004ab
  1. 6
      setup.py
  2. 2
      slither/analyses/data_dependency/data_dependency.py
  3. 22
      slither/core/declarations/import_directive.py
  4. 41
      slither/core/declarations/solidity_import_placeholder.py
  5. 36
      slither/core/declarations/solidity_variables.py
  6. 69
      slither/slithir/convert.py
  7. 9
      slither/slithir/operations/internal_call.py
  8. 4
      slither/slithir/operations/member.py
  9. 2
      slither/slithir/utils/ssa.py
  10. 366
      slither/solc_parsing/expressions/expression_parsing.py
  11. 387
      slither/solc_parsing/expressions/find_variable.py
  12. 22
      slither/solc_parsing/slither_compilation_unit_solc.py
  13. BIN
      tests/ast-parsing/compile/assembly-0.4.0-legacy.zip
  14. BIN
      tests/ast-parsing/compile/assembly-0.4.1-legacy.zip
  15. BIN
      tests/ast-parsing/compile/assembly-0.4.10-legacy.zip
  16. BIN
      tests/ast-parsing/compile/assembly-0.4.11-legacy.zip
  17. BIN
      tests/ast-parsing/compile/assembly-0.4.12-compact.zip
  18. BIN
      tests/ast-parsing/compile/assembly-0.4.12-legacy.zip
  19. BIN
      tests/ast-parsing/compile/assembly-0.4.13-compact.zip
  20. BIN
      tests/ast-parsing/compile/assembly-0.4.13-legacy.zip
  21. BIN
      tests/ast-parsing/compile/assembly-0.4.14-compact.zip
  22. BIN
      tests/ast-parsing/compile/assembly-0.4.14-legacy.zip
  23. BIN
      tests/ast-parsing/compile/assembly-0.4.15-compact.zip
  24. BIN
      tests/ast-parsing/compile/assembly-0.4.15-legacy.zip
  25. BIN
      tests/ast-parsing/compile/assembly-0.4.16-compact.zip
  26. BIN
      tests/ast-parsing/compile/assembly-0.4.16-legacy.zip
  27. BIN
      tests/ast-parsing/compile/assembly-0.4.17-compact.zip
  28. BIN
      tests/ast-parsing/compile/assembly-0.4.17-legacy.zip
  29. BIN
      tests/ast-parsing/compile/assembly-0.4.18-compact.zip
  30. BIN
      tests/ast-parsing/compile/assembly-0.4.18-legacy.zip
  31. BIN
      tests/ast-parsing/compile/assembly-0.4.19-compact.zip
  32. BIN
      tests/ast-parsing/compile/assembly-0.4.19-legacy.zip
  33. BIN
      tests/ast-parsing/compile/assembly-0.4.2-legacy.zip
  34. BIN
      tests/ast-parsing/compile/assembly-0.4.20-compact.zip
  35. BIN
      tests/ast-parsing/compile/assembly-0.4.20-legacy.zip
  36. BIN
      tests/ast-parsing/compile/assembly-0.4.21-compact.zip
  37. BIN
      tests/ast-parsing/compile/assembly-0.4.21-legacy.zip
  38. BIN
      tests/ast-parsing/compile/assembly-0.4.22-compact.zip
  39. BIN
      tests/ast-parsing/compile/assembly-0.4.22-legacy.zip
  40. BIN
      tests/ast-parsing/compile/assembly-0.4.23-compact.zip
  41. BIN
      tests/ast-parsing/compile/assembly-0.4.23-legacy.zip
  42. BIN
      tests/ast-parsing/compile/assembly-0.4.24-compact.zip
  43. BIN
      tests/ast-parsing/compile/assembly-0.4.24-legacy.zip
  44. BIN
      tests/ast-parsing/compile/assembly-0.4.25-compact.zip
  45. BIN
      tests/ast-parsing/compile/assembly-0.4.25-legacy.zip
  46. BIN
      tests/ast-parsing/compile/assembly-0.4.26-compact.zip
  47. BIN
      tests/ast-parsing/compile/assembly-0.4.26-legacy.zip
  48. BIN
      tests/ast-parsing/compile/assembly-0.4.3-legacy.zip
  49. BIN
      tests/ast-parsing/compile/assembly-0.4.4-legacy.zip
  50. BIN
      tests/ast-parsing/compile/assembly-0.4.5-legacy.zip
  51. BIN
      tests/ast-parsing/compile/assembly-0.4.6-legacy.zip
  52. BIN
      tests/ast-parsing/compile/assembly-0.4.7-legacy.zip
  53. BIN
      tests/ast-parsing/compile/assembly-0.4.8-legacy.zip
  54. BIN
      tests/ast-parsing/compile/assembly-0.4.9-legacy.zip
  55. BIN
      tests/ast-parsing/compile/assembly-0.5.0-compact.zip
  56. BIN
      tests/ast-parsing/compile/assembly-0.5.0-legacy.zip
  57. BIN
      tests/ast-parsing/compile/assembly-0.5.1-compact.zip
  58. BIN
      tests/ast-parsing/compile/assembly-0.5.1-legacy.zip
  59. BIN
      tests/ast-parsing/compile/assembly-0.5.10-compact.zip
  60. BIN
      tests/ast-parsing/compile/assembly-0.5.10-legacy.zip
  61. BIN
      tests/ast-parsing/compile/assembly-0.5.11-compact.zip
  62. BIN
      tests/ast-parsing/compile/assembly-0.5.11-legacy.zip
  63. BIN
      tests/ast-parsing/compile/assembly-0.5.12-compact.zip
  64. BIN
      tests/ast-parsing/compile/assembly-0.5.12-legacy.zip
  65. BIN
      tests/ast-parsing/compile/assembly-0.5.13-compact.zip
  66. BIN
      tests/ast-parsing/compile/assembly-0.5.13-legacy.zip
  67. BIN
      tests/ast-parsing/compile/assembly-0.5.14-compact.zip
  68. BIN
      tests/ast-parsing/compile/assembly-0.5.14-legacy.zip
  69. BIN
      tests/ast-parsing/compile/assembly-0.5.15-compact.zip
  70. BIN
      tests/ast-parsing/compile/assembly-0.5.15-legacy.zip
  71. BIN
      tests/ast-parsing/compile/assembly-0.5.16-compact.zip
  72. BIN
      tests/ast-parsing/compile/assembly-0.5.16-legacy.zip
  73. BIN
      tests/ast-parsing/compile/assembly-0.5.17-compact.zip
  74. BIN
      tests/ast-parsing/compile/assembly-0.5.17-legacy.zip
  75. BIN
      tests/ast-parsing/compile/assembly-0.5.2-compact.zip
  76. BIN
      tests/ast-parsing/compile/assembly-0.5.2-legacy.zip
  77. BIN
      tests/ast-parsing/compile/assembly-0.5.3-compact.zip
  78. BIN
      tests/ast-parsing/compile/assembly-0.5.3-legacy.zip
  79. BIN
      tests/ast-parsing/compile/assembly-0.5.4-compact.zip
  80. BIN
      tests/ast-parsing/compile/assembly-0.5.4-legacy.zip
  81. BIN
      tests/ast-parsing/compile/assembly-0.5.5-compact.zip
  82. BIN
      tests/ast-parsing/compile/assembly-0.5.5-legacy.zip
  83. BIN
      tests/ast-parsing/compile/assembly-0.5.6-compact.zip
  84. BIN
      tests/ast-parsing/compile/assembly-0.5.6-legacy.zip
  85. BIN
      tests/ast-parsing/compile/assembly-0.5.7-compact.zip
  86. BIN
      tests/ast-parsing/compile/assembly-0.5.7-legacy.zip
  87. BIN
      tests/ast-parsing/compile/assembly-0.5.8-compact.zip
  88. BIN
      tests/ast-parsing/compile/assembly-0.5.8-legacy.zip
  89. BIN
      tests/ast-parsing/compile/assembly-0.5.9-compact.zip
  90. BIN
      tests/ast-parsing/compile/assembly-0.5.9-legacy.zip
  91. BIN
      tests/ast-parsing/compile/assembly-0.6.0-compact.zip
  92. BIN
      tests/ast-parsing/compile/assembly-0.6.0-legacy.zip
  93. BIN
      tests/ast-parsing/compile/assembly-0.6.1-compact.zip
  94. BIN
      tests/ast-parsing/compile/assembly-0.6.1-legacy.zip
  95. BIN
      tests/ast-parsing/compile/assembly-0.6.10-compact.zip
  96. BIN
      tests/ast-parsing/compile/assembly-0.6.10-legacy.zip
  97. BIN
      tests/ast-parsing/compile/assembly-0.6.11-compact.zip
  98. BIN
      tests/ast-parsing/compile/assembly-0.6.11-legacy.zip
  99. BIN
      tests/ast-parsing/compile/assembly-0.6.12-compact.zip
  100. BIN
      tests/ast-parsing/compile/assembly-0.6.12-legacy.zip
  101. Some files were not shown because too many files have changed in this diff Show More

@ -11,10 +11,10 @@ setup(
install_requires=[
"prettytable>=0.7.2",
"pysha3>=1.0.2",
"crytic-compile>=0.2.1",
# "crytic-compile",
# "crytic-compile>=0.2.1",
"crytic-compile",
],
# dependency_links=["git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile"],
dependency_links=["git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile"],
license="AGPL-3.0",
long_description=open("README.md").read(),
entry_points={

@ -13,6 +13,7 @@ from slither.core.declarations import (
SolidityVariableComposed,
Structure,
)
from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder
from slither.core.variables.variable import Variable
from slither.slithir.operations import Index, OperationWithLValue, InternalCall
from slither.slithir.variables import (
@ -408,6 +409,7 @@ def convert_variable_to_non_ssa(v):
Structure,
Function,
Type,
SolidityImportPlaceHolder,
),
)
return v

@ -1,16 +1,34 @@
from pathlib import Path
from typing import Optional
from slither.core.source_mapping.source_mapping import SourceMapping
class Import(SourceMapping):
def __init__(self, filename: str):
def __init__(self, filename: Path):
super().__init__()
self._filename = filename
self._filename: Path = filename
self._alias: Optional[str] = None
@property
def filename(self) -> str:
"""
Return the absolute filename
:return:
:rtype:
"""
return str(self._filename)
@property
def filename_path(self) -> Path:
"""
Return the absolute filename
:return:
:rtype:
"""
return self._filename
@property

@ -0,0 +1,41 @@
"""
Special variable to model import with renaming
"""
from slither.core.declarations import Import
from slither.core.solidity_types import ElementaryType
from slither.core.variables.variable import Variable
class SolidityImportPlaceHolder(Variable):
"""
Placeholder for import on top level objects
See the example at https://blog.soliditylang.org/2020/09/02/solidity-0.7.1-release-announcement/
In the long term we should remove this and better integrate import aliases
"""
def __init__(self, import_directive: Import):
super().__init__()
assert import_directive.alias is not None
self._import_directive = import_directive
self._name = import_directive.alias
self._type = ElementaryType("string")
self._initialized = True
self._visibility = "private"
self._is_constant = True
@property
def type(self) -> ElementaryType:
return ElementaryType("string")
def __eq__(self, other):
return (
self.__class__ == other.__class__
and self._import_directive.filename == self._import_directive.filename
)
@property
def import_directive(self) -> Import:
return self._import_directive
def __hash__(self):
return hash(str(self.import_directive))

@ -6,7 +6,7 @@ from slither.core.solidity_types import ElementaryType, TypeInformation
from slither.exceptions import SlitherException
if TYPE_CHECKING:
from slither.core.declarations import Import
pass
SOLIDITY_VARIABLES = {
"now": "uint256",
@ -184,37 +184,3 @@ class SolidityFunction:
def __hash__(self):
return hash(self.name)
class SolidityImportPlaceHolder(SolidityVariable):
"""
Placeholder for import on top level objects
See the example at https://blog.soliditylang.org/2020/09/02/solidity-0.7.1-release-announcement/
In the long term we should remove this and better integrate import aliases
"""
def __init__(self, import_directive: "Import"):
assert import_directive.alias is not None
super().__init__(import_directive.alias)
self._import_directive = import_directive
def _check_name(self, name: str):
return True
@property
def type(self) -> ElementaryType:
return ElementaryType("string")
def __eq__(self, other):
return (
self.__class__ == other.__class__
and self.name == other.name
and self._import_directive.filename == self._import_directive.filename
)
@property
def import_directive(self) -> "Import":
return self._import_directive
def __hash__(self):
return hash(str(self.import_directive))

@ -1,4 +1,5 @@
import logging
from pathlib import Path
from typing import List, TYPE_CHECKING, Union, Optional
# pylint: disable= too-many-lines,import-outside-toplevel,too-many-branches,too-many-statements,too-many-nested-blocks
@ -13,6 +14,7 @@ from slither.core.declarations import (
Structure,
)
from slither.core.declarations.function_contract import FunctionContract
from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder
from slither.core.expressions import Identifier, Literal
from slither.core.solidity_types import (
ArrayType,
@ -386,7 +388,11 @@ def propagate_type_and_convert_call(result, node):
ins = result[idx]
if isinstance(ins, TmpCall):
new_ins = extract_tmp_call(ins, node.function.contract)
# If the node.function is a FunctionTopLevel, then it does not have a contract
contract = (
node.function.contract if isinstance(node.function, FunctionContract) else None
)
new_ins = extract_tmp_call(ins, contract)
if new_ins:
new_ins.set_node(ins.node)
ins = new_ins
@ -558,7 +564,11 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals
elif isinstance(ir, InternalCall):
# if its not a tuple, return a singleton
if ir.function is None:
convert_type_of_high_and_internal_level_call(ir, node.function.contract)
func = node.function
function_contract = (
func.contract if isinstance(func, FunctionContract) else None
)
convert_type_of_high_and_internal_level_call(ir, function_contract)
return_type = ir.function.return_type
if return_type:
if len(return_type) == 1:
@ -735,7 +745,7 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals
return None
def extract_tmp_call(ins, contract): # pylint: disable=too-many-locals
def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]): # pylint: disable=too-many-locals
assert isinstance(ins, TmpCall)
if isinstance(ins.called, Variable) and isinstance(ins.called.type, FunctionType):
# If the call is made to a variable member, where the member is this
@ -749,7 +759,7 @@ def extract_tmp_call(ins, contract): # pylint: disable=too-many-locals
return call
if isinstance(ins.ori, Member):
# If there is a call on an inherited contract, it is an internal call or an event
if ins.ori.variable_left in contract.inheritance + [contract]:
if contract and ins.ori.variable_left in contract.inheritance + [contract]:
if str(ins.ori.variable_right) in [f.name for f in contract.functions]:
internalcall = InternalCall(
(ins.ori.variable_right, ins.ori.variable_left.name),
@ -865,6 +875,33 @@ def extract_tmp_call(ins, contract): # pylint: disable=too-many-locals
to_log = "Slither does not support dynamic functions to libraries if functions have the same name"
to_log += f"{[candidate.full_name for candidate in candidates]}"
raise SlithIRError(to_log)
if isinstance(ins.ori.variable_left, SolidityImportPlaceHolder):
# For top level import, where the import statement renames the filename
# See https://blog.soliditylang.org/2020/09/02/solidity-0.7.1-release-announcement/
current_path = Path(ins.ori.variable_left.source_mapping["filename_absolute"]).parent
target = str(
Path(current_path, ins.ori.variable_left.import_directive.filename).absolute()
)
top_level_function_targets = [
f
for f in ins.compilation_unit.functions_top_level
if f.source_mapping["filename_absolute"] == target
and f.name == ins.ori.variable_right
and len(f.parameters) == ins.nbr_arguments
]
internalcall = InternalCall(
(ins.ori.variable_right, ins.ori.variable_left.name),
ins.nbr_arguments,
ins.lvalue,
ins.type_call,
)
internalcall.set_expression(ins.expression)
internalcall.call_id = ins.call_id
internalcall.function_candidates = top_level_function_targets
return internalcall
msgcall = HighLevelCall(
ins.ori.variable_left,
ins.ori.variable_right,
@ -1355,22 +1392,30 @@ def _convert_to_structure_to_list(return_type: Type) -> List[Type]:
return [return_type.type]
def convert_type_of_high_and_internal_level_call(ir: Operation, contract: Contract):
def convert_type_of_high_and_internal_level_call(ir: Operation, contract: Optional[Contract]):
func = None
if isinstance(ir, InternalCall):
candidates = [
f
for f in contract.functions
if f.name == ir.function_name
and f.contract_declarer.name == ir.contract_name
and len(f.parameters) == len(ir.arguments)
]
if ir.function_candidates:
# This path is taken only for SolidityImportPlaceHolder
# Here we have already done a filtering on the potential targets
candidates = ir.function_candidates
else:
candidates = [
f
for f in contract.functions
if f.name == ir.function_name
and f.contract_declarer.name == ir.contract_name
and len(f.parameters) == len(ir.arguments)
]
func = _find_function_from_parameter(ir, candidates)
if not func:
assert contract
func = contract.get_state_variable_from_name(ir.function_name)
else:
assert isinstance(ir, HighLevelCall)
assert contract
candidates = [
f

@ -1,3 +1,4 @@
from typing import Union, Tuple, List, Optional
from slither.core.declarations import Modifier
from slither.core.declarations.function import Function
from slither.core.declarations.function_contract import FunctionContract
@ -6,7 +7,9 @@ from slither.slithir.operations.lvalue import OperationWithLValue
class InternalCall(Call, OperationWithLValue): # pylint: disable=too-many-instance-attributes
def __init__(self, function, nbr_arguments, result, type_call):
def __init__(
self, function: Union[Function, Tuple[str, str]], nbr_arguments, result, type_call
):
super().__init__()
self._contract_name = ""
if isinstance(function, Function):
@ -21,6 +24,10 @@ class InternalCall(Call, OperationWithLValue): # pylint: disable=too-many-insta
self._nbr_arguments = nbr_arguments
self._type_call = type_call
self._lvalue = result
# function_candidates is only used as an helper to retrieve the "function" object
# For top level function called through a import renamed
# See SolidityImportPlaceHolder usages
self.function_candidates: Optional[List[Function]] = None
@property
def read(self):

@ -1,5 +1,6 @@
from slither.core.declarations import Contract, Function
from slither.core.declarations.enum import Enum
from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder
from slither.slithir.operations.lvalue import OperationWithLValue
from slither.slithir.utils.utils import is_valid_rvalue
from slither.slithir.variables.constant import Constant
@ -20,8 +21,9 @@ class Member(OperationWithLValue):
# }
# }
assert is_valid_rvalue(variable_left) or isinstance(
variable_left, (Contract, Enum, Function)
variable_left, (Contract, Enum, Function, SolidityImportPlaceHolder)
)
assert isinstance(variable_right, Constant)
assert isinstance(result, ReferenceVariable)
super().__init__()

@ -9,6 +9,7 @@ from slither.core.declarations import (
SolidityVariable,
Structure,
)
from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder
from slither.core.solidity_types.type import Type
from slither.core.variables.local_variable import LocalVariable
from slither.core.variables.state_variable import StateVariable
@ -610,6 +611,7 @@ def get(
Structure,
Function,
Type,
SolidityImportPlaceHolder,
),
) # type for abi.decode(.., t)
return variable

@ -1,19 +1,10 @@
import logging
import re
from typing import Dict, TYPE_CHECKING, Optional, Union, List, Tuple
from typing import Dict, TYPE_CHECKING
from slither.core.declarations import Event, Enum, Structure
from slither.core.declarations.contract import Contract
from slither.core.declarations.function import Function
from slither.core.declarations.function_contract import FunctionContract
from slither.core.declarations.solidity_variables import (
SOLIDITY_FUNCTIONS,
SOLIDITY_VARIABLES,
SOLIDITY_VARIABLES_COMPOSED,
SolidityFunction,
SolidityVariable,
SolidityVariableComposed,
SolidityImportPlaceHolder,
)
from slither.core.expressions.assignment_operation import (
AssignmentOperation,
@ -41,359 +32,18 @@ from slither.core.expressions.unary_operation import UnaryOperation, UnaryOperat
from slither.core.solidity_types import (
ArrayType,
ElementaryType,
FunctionType,
MappingType,
)
from slither.core.variables.variable import Variable
from slither.exceptions import SlitherError
from slither.solc_parsing.exceptions import ParsingError, VariableNotFound
from slither.solc_parsing.expressions.find_variable import CallerContext, find_variable
from slither.solc_parsing.solidity_types.type_parsing import UnknownType, parse_type
if TYPE_CHECKING:
from slither.core.expressions.expression import Expression
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.core.compilation_unit import SlitherCompilationUnit
logger = logging.getLogger("ExpressionParsing")
# pylint: disable=anomalous-backslash-in-string,import-outside-toplevel,too-many-branches,too-many-locals
###################################################################################
###################################################################################
# region Helpers
###################################################################################
###################################################################################
CallerContext = Union["ContractSolc", "FunctionSolc"]
def get_pointer_name(variable: Variable):
curr_type = variable.type
while isinstance(curr_type, (ArrayType, MappingType)):
if isinstance(curr_type, ArrayType):
curr_type = curr_type.type
else:
assert isinstance(curr_type, MappingType)
curr_type = curr_type.type_to
if isinstance(curr_type, FunctionType):
return variable.name + curr_type.parameters_signature
return None
def _find_variable_from_ref_declaration(
referenced_declaration: Optional[int],
all_contracts: List["Contract"],
all_functions_parser: List["FunctionSolc"],
) -> Optional[Union[Contract, Function]]:
if referenced_declaration is None:
return None
# id of the contracts is the referenced declaration
# This is not true for the functions, as we dont always have the referenced_declaration
# But maybe we could? (TODO)
for contract_candidate in all_contracts:
if contract_candidate.id == referenced_declaration:
return contract_candidate
for function_candidate in all_functions_parser:
if (
function_candidate.referenced_declaration == referenced_declaration
and not function_candidate.underlying_function.is_shadowed
):
return function_candidate.underlying_function
return None
def _find_variable_in_function_parser(
var_name: str,
function_parser: Optional["FunctionSolc"],
referenced_declaration: Optional[int] = None,
) -> Optional[Variable]:
if function_parser is None:
return None
# We look for variable declared with the referencedDeclaration attr
func_variables_renamed = function_parser.variables_renamed
if referenced_declaration and referenced_declaration in func_variables_renamed:
return func_variables_renamed[referenced_declaration].underlying_variable
# If not found, check for name
func_variables = function_parser.underlying_function.variables_as_dict
if var_name in func_variables:
return func_variables[var_name]
# A local variable can be a pointer
# for example
# function test(function(uint) internal returns(bool) t) interna{
# Will have a local variable t which will match the signature
# t(uint256)
func_variables_ptr = {
get_pointer_name(f): f for f in function_parser.underlying_function.variables
}
if var_name and var_name in func_variables_ptr:
return func_variables_ptr[var_name]
return None
def _find_top_level(
var_name: str, sl: "SlitherCompilationUnit"
) -> Optional[Union[Enum, Structure, SolidityVariable]]:
structures_top_level = sl.structures_top_level
for st in structures_top_level:
if st.name == var_name:
return st
enums_top_level = sl.enums_top_level
for enum in enums_top_level:
if enum.name == var_name:
return enum
for import_directive in sl.import_directives:
if import_directive.alias == var_name:
return SolidityImportPlaceHolder(import_directive)
return None
def _find_in_contract(
var_name: str,
contract: Optional[Contract],
contract_declarer: Optional[Contract],
is_super: bool,
) -> Optional[Union[Variable, Function, Contract, Event, Enum, Structure,]]:
if contract is None or contract_declarer is None:
return None
# variable are looked from the contract declarer
contract_variables = contract_declarer.variables_as_dict
if var_name in contract_variables:
return contract_variables[var_name]
# A state variable can be a pointer
conc_variables_ptr = {get_pointer_name(f): f for f in contract_declarer.variables}
if var_name and var_name in conc_variables_ptr:
return conc_variables_ptr[var_name]
if is_super:
getter_available = lambda f: f.functions_declared
d = {f.canonical_name: f for f in contract.functions}
functions = {
f.full_name: f
for f in contract_declarer.available_elements_from_inheritances(
d, getter_available
).values()
}
else:
functions = contract.available_functions_as_dict()
if var_name in functions:
return functions[var_name]
if is_super:
getter_available = lambda m: m.modifiers_declared
d = {m.canonical_name: m for m in contract.modifiers}
modifiers = {
m.full_name: m
for m in contract_declarer.available_elements_from_inheritances(
d, getter_available
).values()
}
else:
modifiers = contract.available_modifiers_as_dict()
if var_name in modifiers:
return modifiers[var_name]
# structures are looked on the contract declarer
structures = contract.structures_as_dict
if var_name in structures:
return structures[var_name]
events = contract.events_as_dict
if var_name in events:
return events[var_name]
enums = contract.enums_as_dict
if var_name in enums:
return enums[var_name]
# If the enum is refered as its name rather than its canonicalName
enums = {e.name: e for e in contract.enums}
if var_name in enums:
return enums[var_name]
return None
def _find_variable_init(
caller_context: CallerContext,
) -> Tuple[
List[Contract],
Union[List["FunctionSolc"]],
"SlitherCompilationUnit",
"SlitherCompilationUnitSolc",
]:
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.declarations.function import FunctionSolc
direct_contracts: List[Contract]
direct_functions_parser: List[FunctionSolc]
if isinstance(caller_context, SlitherCompilationUnitSolc):
direct_contracts = []
direct_functions_parser = []
sl = caller_context.compilation_unit
sl_parser = caller_context
elif isinstance(caller_context, ContractSolc):
direct_contracts = [caller_context.underlying_contract]
direct_functions_parser = caller_context.functions_parser + caller_context.modifiers_parser
sl = caller_context.slither_parser.compilation_unit
sl_parser = caller_context.slither_parser
elif isinstance(caller_context, FunctionSolc):
if caller_context.contract_parser:
direct_contracts = [caller_context.contract_parser.underlying_contract]
direct_functions_parser = (
caller_context.contract_parser.functions_parser
+ caller_context.contract_parser.modifiers_parser
)
else:
# Top level functions
direct_contracts = []
direct_functions_parser = []
sl = caller_context.underlying_function.compilation_unit
sl_parser = caller_context.slither_parser
else:
raise SlitherError(
f"{type(caller_context)} ({caller_context} is not valid for find_variable"
)
return direct_contracts, direct_functions_parser, sl, sl_parser
def find_variable(
var_name: str,
caller_context: CallerContext,
referenced_declaration: Optional[int] = None,
is_super=False,
) -> Union[
Variable,
Function,
Contract,
SolidityVariable,
SolidityFunction,
Event,
Enum,
Structure,
]:
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.contract import ContractSolc
# variable are looked from the contract declarer
# functions can be shadowed, but are looked from the contract instance, rather than the contract declarer
# the difference between function and variable come from the fact that an internal call, or an variable access
# in a function does not behave similariy, for example in:
# contract C{
# function f(){
# state_var = 1
# f2()
# }
# state_var will refer to C.state_var, no mater if C is inherited
# while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or
# the contract inheriting from C)
# for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact
# structure/enums cannot be shadowed
direct_contracts, direct_functions_parser, sl, sl_parser = _find_variable_init(caller_context)
all_contracts = sl.contracts
all_functions_parser = sl_parser.all_functions_and_modifiers_parser
# Only look for reference declaration in the direct contract, see comment at the end
# Reference looked are split between direct and all
# Because functions are copied between contracts, two functions can have the same ref
# So we need to first look with respect to the direct context
ret = _find_variable_from_ref_declaration(
referenced_declaration, direct_contracts, direct_functions_parser
)
if ret:
return ret
function_parser: Optional[FunctionSolc] = (
caller_context if isinstance(caller_context, FunctionSolc) else None
)
ret = _find_variable_in_function_parser(var_name, function_parser, referenced_declaration)
if ret:
return ret
contract: Optional[Contract] = None
contract_declarer: Optional[Contract] = None
if isinstance(caller_context, ContractSolc):
contract = caller_context.underlying_contract
contract_declarer = caller_context.underlying_contract
elif isinstance(caller_context, FunctionSolc):
underlying_func = caller_context.underlying_function
# If contract_parser is set to None, then underlying_function is a functionContract
assert isinstance(underlying_func, FunctionContract)
contract = underlying_func.contract
contract_declarer = underlying_func.contract_declarer
ret = _find_in_contract(var_name, contract, contract_declarer, is_super)
if ret:
return ret
# Could refer to any enum
all_enumss = [c.enums_as_dict for c in sl.contracts]
all_enums = {k: v for d in all_enumss for k, v in d.items()}
if var_name in all_enums:
return all_enums[var_name]
contracts = sl.contracts_as_dict
if var_name in contracts:
return contracts[var_name]
if var_name in SOLIDITY_VARIABLES:
return SolidityVariable(var_name)
if var_name in SOLIDITY_FUNCTIONS:
return SolidityFunction(var_name)
# Top level must be at the end, if nothing else was found
ret = _find_top_level(var_name, sl)
if ret:
return ret
# Look from reference declaration in all the contracts at the end
# Because they are many instances where this can't be trusted
# For example in
# contract A{
# function _f() internal view returns(uint){
# return 1;
# }
#
# function get() public view returns(uint){
# return _f();
# }
# }
#
# contract B is A{
# function _f() internal view returns(uint){
# return 2;
# }
#
# }
# get's AST will say that the ref declaration for _f() is A._f(), but in the context of B, its not
ret = _find_variable_from_ref_declaration(
referenced_declaration, all_contracts, all_functions_parser
)
if ret:
return ret
raise VariableNotFound("Variable not found: {} (context {})".format(var_name, caller_context))
# endregion
###################################################################################
###################################################################################
# region Filtering
###################################################################################
###################################################################################
@ -799,7 +449,9 @@ def parse_expression(expression: Dict, caller_context: CallerContext) -> "Expres
else:
referenced_declaration = None
var = find_variable(value, caller_context, referenced_declaration)
var, was_created = find_variable(value, caller_context, referenced_declaration)
if was_created:
var.set_offset(src, caller_context.compilation_unit)
identifier = Identifier(var)
identifier.set_offset(src, caller_context.compilation_unit)
@ -849,9 +501,11 @@ def parse_expression(expression: Dict, caller_context: CallerContext) -> "Expres
member_expression = parse_expression(children[0], caller_context)
if str(member_expression) == "super":
super_name = parse_super_name(expression, is_compact_ast)
var = find_variable(super_name, caller_context, is_super=True)
var, was_created = find_variable(super_name, caller_context, is_super=True)
if var is None:
raise VariableNotFound("Variable not found: {}".format(super_name))
if was_created:
var.set_offset(src, caller_context.compilation_unit)
sup = SuperIdentifier(var)
sup.set_offset(src, caller_context.compilation_unit)
return sup
@ -975,7 +629,9 @@ def parse_expression(expression: Dict, caller_context: CallerContext) -> "Expres
else:
referenced_declaration = None
var = find_variable(value, caller_context, referenced_declaration)
var, was_created = find_variable(value, caller_context, referenced_declaration)
if was_created:
var.set_offset(src, caller_context.compilation_unit)
identifier = Identifier(var)
identifier.set_offset(src, caller_context.compilation_unit)

@ -0,0 +1,387 @@
from typing import TYPE_CHECKING, Optional, Union, List, Tuple
from slither.core.declarations import Event, Enum, Structure
from slither.core.declarations.contract import Contract
from slither.core.declarations.function import Function
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 (
SOLIDITY_FUNCTIONS,
SOLIDITY_VARIABLES,
SolidityFunction,
SolidityVariable,
)
from slither.core.solidity_types import (
ArrayType,
FunctionType,
MappingType,
)
from slither.core.variables.variable import Variable
from slither.exceptions import SlitherError
from slither.solc_parsing.exceptions import VariableNotFound
if TYPE_CHECKING:
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.core.compilation_unit import SlitherCompilationUnit
# pylint: disable=import-outside-toplevel,too-many-branches,too-many-locals
CallerContext = Union["ContractSolc", "FunctionSolc"]
def _get_pointer_name(variable: Variable):
curr_type = variable.type
while isinstance(curr_type, (ArrayType, MappingType)):
if isinstance(curr_type, ArrayType):
curr_type = curr_type.type
else:
assert isinstance(curr_type, MappingType)
curr_type = curr_type.type_to
if isinstance(curr_type, FunctionType):
return variable.name + curr_type.parameters_signature
return None
def _find_variable_from_ref_declaration(
referenced_declaration: Optional[int],
all_contracts: List["Contract"],
all_functions_parser: List["FunctionSolc"],
) -> Optional[Union[Contract, Function]]:
if referenced_declaration is None:
return None
# id of the contracts is the referenced declaration
# This is not true for the functions, as we dont always have the referenced_declaration
# But maybe we could? (TODO)
for contract_candidate in all_contracts:
if contract_candidate.id == referenced_declaration:
return contract_candidate
for function_candidate in all_functions_parser:
if (
function_candidate.referenced_declaration == referenced_declaration
and not function_candidate.underlying_function.is_shadowed
):
return function_candidate.underlying_function
return None
def _find_variable_in_function_parser(
var_name: str,
function_parser: Optional["FunctionSolc"],
referenced_declaration: Optional[int] = None,
) -> Optional[Variable]:
if function_parser is None:
return None
# We look for variable declared with the referencedDeclaration attr
func_variables_renamed = function_parser.variables_renamed
if referenced_declaration and referenced_declaration in func_variables_renamed:
return func_variables_renamed[referenced_declaration].underlying_variable
# If not found, check for name
func_variables = function_parser.underlying_function.variables_as_dict
if var_name in func_variables:
return func_variables[var_name]
# A local variable can be a pointer
# for example
# function test(function(uint) internal returns(bool) t) interna{
# Will have a local variable t which will match the signature
# t(uint256)
func_variables_ptr = {
_get_pointer_name(f): f for f in function_parser.underlying_function.variables
}
if var_name and var_name in func_variables_ptr:
return func_variables_ptr[var_name]
return None
def _find_top_level(
var_name: str, sl: "SlitherCompilationUnit"
) -> Tuple[Optional[Union[Enum, Structure, SolidityImportPlaceHolder]], bool]:
"""
Return the top level variable use, and a boolean indicating if the variable returning was cretead
If the variable was created, it has no source_mapping
:param var_name:
:type var_name:
:param sl:
:type sl:
:return:
:rtype:
"""
structures_top_level = sl.structures_top_level
for st in structures_top_level:
if st.name == var_name:
return st, False
enums_top_level = sl.enums_top_level
for enum in enums_top_level:
if enum.name == var_name:
return enum, False
for import_directive in sl.import_directives:
if import_directive.alias == var_name:
new_val = SolidityImportPlaceHolder(import_directive)
return new_val, True
return None, False
def _find_in_contract(
var_name: str,
contract: Optional[Contract],
contract_declarer: Optional[Contract],
is_super: bool,
) -> Optional[Union[Variable, Function, Contract, Event, Enum, Structure,]]:
if contract is None or contract_declarer is None:
return None
# variable are looked from the contract declarer
contract_variables = contract_declarer.variables_as_dict
if var_name in contract_variables:
return contract_variables[var_name]
# A state variable can be a pointer
conc_variables_ptr = {_get_pointer_name(f): f for f in contract_declarer.variables}
if var_name and var_name in conc_variables_ptr:
return conc_variables_ptr[var_name]
if is_super:
getter_available = lambda f: f.functions_declared
d = {f.canonical_name: f for f in contract.functions}
functions = {
f.full_name: f
for f in contract_declarer.available_elements_from_inheritances(
d, getter_available
).values()
}
else:
functions = contract.available_functions_as_dict()
if var_name in functions:
return functions[var_name]
if is_super:
getter_available = lambda m: m.modifiers_declared
d = {m.canonical_name: m for m in contract.modifiers}
modifiers = {
m.full_name: m
for m in contract_declarer.available_elements_from_inheritances(
d, getter_available
).values()
}
else:
modifiers = contract.available_modifiers_as_dict()
if var_name in modifiers:
return modifiers[var_name]
# structures are looked on the contract declarer
structures = contract.structures_as_dict
if var_name in structures:
return structures[var_name]
events = contract.events_as_dict
if var_name in events:
return events[var_name]
enums = contract.enums_as_dict
if var_name in enums:
return enums[var_name]
# If the enum is refered as its name rather than its canonicalName
enums = {e.name: e for e in contract.enums}
if var_name in enums:
return enums[var_name]
return None
def _find_variable_init(
caller_context: CallerContext,
) -> Tuple[
List[Contract],
Union[List["FunctionSolc"]],
"SlitherCompilationUnit",
"SlitherCompilationUnitSolc",
]:
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.declarations.function import FunctionSolc
direct_contracts: List[Contract]
direct_functions_parser: List[FunctionSolc]
if isinstance(caller_context, SlitherCompilationUnitSolc):
direct_contracts = []
direct_functions_parser = []
sl = caller_context.compilation_unit
sl_parser = caller_context
elif isinstance(caller_context, ContractSolc):
direct_contracts = [caller_context.underlying_contract]
direct_functions_parser = caller_context.functions_parser + caller_context.modifiers_parser
sl = caller_context.slither_parser.compilation_unit
sl_parser = caller_context.slither_parser
elif isinstance(caller_context, FunctionSolc):
if caller_context.contract_parser:
direct_contracts = [caller_context.contract_parser.underlying_contract]
direct_functions_parser = (
caller_context.contract_parser.functions_parser
+ caller_context.contract_parser.modifiers_parser
)
else:
# Top level functions
direct_contracts = []
direct_functions_parser = []
sl = caller_context.underlying_function.compilation_unit
sl_parser = caller_context.slither_parser
else:
raise SlitherError(
f"{type(caller_context)} ({caller_context} is not valid for find_variable"
)
return direct_contracts, direct_functions_parser, sl, sl_parser
def find_variable(
var_name: str,
caller_context: CallerContext,
referenced_declaration: Optional[int] = None,
is_super=False,
) -> Tuple[
Union[
Variable,
Function,
Contract,
SolidityVariable,
SolidityFunction,
Event,
Enum,
Structure,
],
bool,
]:
"""
Return the variable found and a boolean indicating if the variable was created
If the variable was created, it has no source mapping, and it the caller must add it
:param var_name:
:type var_name:
:param caller_context:
:type caller_context:
:param referenced_declaration:
:type referenced_declaration:
:param is_super:
:type is_super:
:return:
:rtype:
"""
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.contract import ContractSolc
# variable are looked from the contract declarer
# functions can be shadowed, but are looked from the contract instance, rather than the contract declarer
# the difference between function and variable come from the fact that an internal call, or an variable access
# in a function does not behave similariy, for example in:
# contract C{
# function f(){
# state_var = 1
# f2()
# }
# state_var will refer to C.state_var, no mater if C is inherited
# while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or
# the contract inheriting from C)
# for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact
# structure/enums cannot be shadowed
direct_contracts, direct_functions_parser, sl, sl_parser = _find_variable_init(caller_context)
all_contracts = sl.contracts
all_functions_parser = sl_parser.all_functions_and_modifiers_parser
# Only look for reference declaration in the direct contract, see comment at the end
# Reference looked are split between direct and all
# Because functions are copied between contracts, two functions can have the same ref
# So we need to first look with respect to the direct context
ret = _find_variable_from_ref_declaration(
referenced_declaration, direct_contracts, direct_functions_parser
)
if ret:
return ret, False
function_parser: Optional[FunctionSolc] = (
caller_context if isinstance(caller_context, FunctionSolc) else None
)
ret = _find_variable_in_function_parser(var_name, function_parser, referenced_declaration)
if ret:
return ret, False
contract: Optional[Contract] = None
contract_declarer: Optional[Contract] = None
if isinstance(caller_context, ContractSolc):
contract = caller_context.underlying_contract
contract_declarer = caller_context.underlying_contract
elif isinstance(caller_context, FunctionSolc):
underlying_func = caller_context.underlying_function
if isinstance(underlying_func, FunctionContract):
contract = underlying_func.contract
contract_declarer = underlying_func.contract_declarer
else:
assert isinstance(underlying_func, FunctionTopLevel)
ret = _find_in_contract(var_name, contract, contract_declarer, is_super)
if ret:
return ret, False
# Could refer to any enum
all_enumss = [c.enums_as_dict for c in sl.contracts]
all_enums = {k: v for d in all_enumss for k, v in d.items()}
if var_name in all_enums:
return all_enums[var_name], False
contracts = sl.contracts_as_dict
if var_name in contracts:
return contracts[var_name], False
if var_name in SOLIDITY_VARIABLES:
return SolidityVariable(var_name), False
if var_name in SOLIDITY_FUNCTIONS:
return SolidityFunction(var_name), False
# Top level must be at the end, if nothing else was found
ret, var_was_created = _find_top_level(var_name, sl)
if ret:
return ret, var_was_created
# Look from reference declaration in all the contracts at the end
# Because they are many instances where this can't be trusted
# For example in
# contract A{
# function _f() internal view returns(uint){
# return 1;
# }
#
# function get() public view returns(uint){
# return _f();
# }
# }
#
# contract B is A{
# function _f() internal view returns(uint){
# return 2;
# }
#
# }
# get's AST will say that the ref declaration for _f() is A._f(), but in the context of B, its not
ret = _find_variable_from_ref_declaration(
referenced_declaration, all_contracts, all_functions_parser
)
if ret:
return ret, False
raise VariableNotFound("Variable not found: {} (context {})".format(var_name, caller_context))

@ -2,6 +2,7 @@ import json
import logging
import os
import re
from pathlib import Path
from typing import List, Dict
from slither.analyses.data_dependency.data_dependency import compute_dependency
@ -181,12 +182,28 @@ class SlitherCompilationUnitSolc:
self._compilation_unit.pragma_directives.append(pragma)
elif top_level_data[self.get_key()] == "ImportDirective":
if self.is_compact_ast:
import_directive = Import(top_level_data["absolutePath"])
import_directive = Import(
Path(
self._compilation_unit.crytic_compile.working_dir,
top_level_data["absolutePath"],
)
)
# TODO investigate unitAlias in version < 0.7 and legacy ast
if "unitAlias" in top_level_data:
import_directive.alias = top_level_data["unitAlias"]
else:
import_directive = Import(top_level_data["attributes"].get("absolutePath", ""))
import_directive = Import(
Path(
self._compilation_unit.crytic_compile.working_dir,
top_level_data["attributes"].get("absolutePath", ""),
)
)
# TODO investigate unitAlias in version < 0.7 and legacy ast
if (
"attributes" in top_level_data
and "unitAlias" in top_level_data["attributes"]
):
import_directive.alias = top_level_data["attributes"]["unitAlias"]
import_directive.set_offset(top_level_data["src"], self._compilation_unit)
self._compilation_unit.import_directives.append(import_directive)
@ -211,6 +228,7 @@ class SlitherCompilationUnitSolc:
self._variables_top_level_parser.append(var_parser)
elif top_level_data[self.get_key()] == "FunctionDefinition":
func = FunctionTopLevel(self._compilation_unit)
func.set_offset(top_level_data["src"], self._compilation_unit)
func_parser = FunctionSolc(func, top_level_data, None, self)
self._compilation_unit.functions_top_level.append(func)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save