cleanup code and add test

pull/2403/head
alpharush 8 months ago
parent e2d00474ad
commit 5a6ef0d5a6
  1. 3
      slither/core/declarations/function_contract.py
  2. 3
      slither/core/declarations/using_for_top_level.py
  3. 44
      slither/core/scope/scope.py
  4. 99
      slither/slither.py
  5. 23
      slither/slithir/convert.py
  6. 13
      slither/solc_parsing/declarations/contract.py
  7. 3
      slither/solc_parsing/declarations/using_for_top_level.py
  8. 6
      slither/solc_parsing/expressions/find_variable.py
  9. 33
      slither/solc_parsing/slither_compilation_unit_solc.py
  10. 13
      slither/solc_parsing/solidity_types/type_parsing.py
  11. 4
      slither/visitors/slithir/expression_to_slithir.py
  12. 1
      tests/e2e/solc_parsing/test_ast_parsing.py
  13. BIN
      tests/e2e/solc_parsing/test_data/compile/scope/inherited_function_scope.sol-0.8.24-compact.zip
  14. 12
      tests/e2e/solc_parsing/test_data/expected/scope/inherited_function_scope.sol-0.8.24-compact.json
  15. 7
      tests/e2e/solc_parsing/test_data/scope/inherited_function_scope.sol
  16. 9
      tests/e2e/solc_parsing/test_data/scope/scope_a.sol
  17. 1
      tests/e2e/solc_parsing/test_data/scope/scope_z.sol

@ -65,6 +65,9 @@ class FunctionContract(Function, ContractLevel):
@property
def file_scope(self) -> "FileScope":
# This is the contract declarer's file scope because inherited functions have access
# to the file scope which their declared in. This scope may contain references not
# available in the child contract's scope. See inherited_function_scope.sol for an example.
return self.contract_declarer.file_scope
# endregion

@ -1,6 +1,5 @@
from typing import TYPE_CHECKING, List, Dict, Union
from typing import TYPE_CHECKING
from slither.core.solidity_types.type import Type
from slither.core.declarations.top_level import TopLevel
from slither.utils.using_for import USING_FOR

@ -60,20 +60,18 @@ class FileScope:
def add_accessible_scopes(self) -> bool: # pylint: disable=too-many-branches
"""
Add information from accessible scopes. Return true if new information was obtained
:return:
:rtype:
"""
learn_something = False
# This is a hacky way to support using for directives on user defined types and user defined functions
# since it is not reflected in the "exportedSymbols" field of the AST.
for new_scope in self.accessible_scopes:
# To support using for directives on user defined types and user defined functions,
# we need to propagate the using for directives from the imported file to the importing file
# since it is not reflected in the "exportedSymbols" field of the AST.
if not new_scope.using_for_directives.issubset(self.using_for_directives):
self.using_for_directives |= new_scope.using_for_directives
learn_something = True
print("using_for_directives", learn_something)
if not _dict_contain(new_scope.type_aliases, self.type_aliases):
self.type_aliases.update(new_scope.type_aliases)
learn_something = True
@ -81,41 +79,13 @@ class FileScope:
self.functions |= new_scope.functions
learn_something = True
# Hack to get around https://github.com/ethereum/solidity/pull/11881
# To get around this bug for aliases https://github.com/ethereum/solidity/pull/11881,
# we propagate the exported_symbols from the imported file to the importing file
# See tests/e2e/solc_parsing/test_data/top-level-nested-import-0.7.1.sol
if not new_scope.exported_symbols.issubset(self.exported_symbols):
self.exported_symbols |= new_scope.exported_symbols
learn_something = True
# if not new_scope.imports.issubset(self.imports):
# self.imports |= new_scope.imports
# learn_something = True
# if not _dict_contain(new_scope.contracts, self.contracts):
# self.contracts.update(new_scope.contracts)
# learn_something = True
# if not new_scope.custom_errors.issubset(self.custom_errors):
# self.custom_errors |= new_scope.custom_errors
# learn_something = True
# if not _dict_contain(new_scope.enums, self.enums):
# self.enums.update(new_scope.enums)
# learn_something = True
# if not new_scope.events.issubset(self.events):
# self.events |= new_scope.events
# learn_something = True
# if not new_scope.functions.issubset(self.functions):
# self.functions |= new_scope.functions
# learn_something = True
# if not new_scope.using_for_directives.issubset(self.using_for_directives):
# self.using_for_directives |= new_scope.using_for_directives
# learn_something = True
# if not new_scope.pragmas.issubset(self.pragmas):
# self.pragmas |= new_scope.pragmas
# learn_something = True
# if not _dict_contain(new_scope.structures, self.structures):
# self.structures.update(new_scope.structures)
# learn_something = True
# if not _dict_contain(new_scope.variables, self.variables):
# self.variables.update(new_scope.variables)
# learn_something = True
# This is need to support aliasing when we do a late lookup using SolidityImportPlaceholder
if not _dict_contain(new_scope.renaming, self.renaming):
self.renaming.update(new_scope.renaming)

@ -27,21 +27,23 @@ def _check_common_things(
) -> None:
if not issubclass(cls, base_cls) or cls is base_cls:
raise Exception(
raise SlitherError(
f"You can't register {cls!r} as a {thing_name}. You need to pass a class that inherits from {base_cls.__name__}"
)
if any(type(obj) == cls for obj in instances_list): # pylint: disable=unidiomatic-typecheck
raise Exception(f"You can't register {cls!r} twice.")
raise SlitherError(f"You can't register {cls!r} twice.")
def _update_file_scopes(candidates: ValuesView[FileScope]):
def _update_file_scopes(sol_parser: SlitherCompilationUnitSolc):
"""
Because solc's import allows cycle in the import
We iterate until we aren't adding new information to the scope
Since all definitions in a file are exported by default, including definitions from its (transitive) dependencies,
we can identify all top level items that could possibly be referenced within the file from its exportedSymbols.
It is not as straightforward for user defined types and functions as well as aliasing. See add_accessible_scopes for more details.
"""
candidates = sol_parser.compilation_unit.scopes.values()
learned_something = False
# Because solc's import allows cycle in the import graph, iterate until we aren't adding new information to the scope.
while True:
for candidate in candidates:
learned_something |= candidate.add_accessible_scopes()
@ -49,6 +51,42 @@ def _update_file_scopes(candidates: ValuesView[FileScope]):
break
learned_something = False
for scope in candidates:
for refId in scope.exported_symbols:
if refId in sol_parser.contracts_by_id:
contract = sol_parser.contracts_by_id[refId]
scope.contracts[contract.name] = contract
elif refId in sol_parser.functions_by_id:
functions = sol_parser.functions_by_id[refId]
assert len(functions) == 1
function = functions[0]
scope.functions.add(function)
elif refId in sol_parser._imports_by_id:
import_directive = sol_parser._imports_by_id[refId]
scope.imports.add(import_directive)
elif refId in sol_parser._top_level_variables_by_id:
top_level_variable = sol_parser._top_level_variables_by_id[refId]
scope.variables[top_level_variable.name] = top_level_variable
elif refId in sol_parser._top_level_events_by_id:
top_level_event = sol_parser._top_level_events_by_id[refId]
scope.events.add(top_level_event)
elif refId in sol_parser._top_level_structures_by_id:
top_level_struct = sol_parser._top_level_structures_by_id[refId]
scope.structures[top_level_struct.name] = top_level_struct
elif refId in sol_parser._top_level_type_aliases_by_id:
top_level_type_alias = sol_parser._top_level_type_aliases_by_id[refId]
scope.type_aliases[top_level_type_alias.name] = top_level_type_alias
elif refId in sol_parser._top_level_enums_by_id:
top_level_enum = sol_parser._top_level_enums_by_id[refId]
scope.enums[top_level_enum.name] = top_level_enum
elif refId in sol_parser._top_level_errors_by_id:
top_level_custom_error = sol_parser._top_level_errors_by_id[refId]
scope.custom_errors.add(top_level_custom_error)
else:
logger.warning(
f"Failed to resolved name for reference id {refId} in {scope.filename}."
)
class Slither(
SlitherCore
@ -118,12 +156,9 @@ class Slither(
sol_parser.parse_top_level_items(ast, path)
self.add_source_code(path)
_update_file_scopes(compilation_unit_slither.scopes.values())
# First we save all the contracts in a dict
# the key is the contractid
for contract in sol_parser._underlying_contract_to_parser:
if contract.name.startswith("SlitherInternalTopLevelContract"):
raise Exception(
raise SlitherError(
# region multi-line-string
"""Your codebase has a contract named 'SlitherInternalTopLevelContract'.
Please rename it, this name is reserved for Slither's internals"""
@ -131,48 +166,8 @@ class Slither(
)
sol_parser._contracts_by_id[contract.id] = contract
sol_parser._compilation_unit.contracts.append(contract)
print("avalilable")
for k, v in sol_parser.contracts_by_id.items():
print(k, v.name)
for scope in compilation_unit_slither.scopes.values():
for refId in scope.exported_symbols:
print("scope", scope)
print("target", refId)
if refId in sol_parser.contracts_by_id:
contract = sol_parser.contracts_by_id[refId]
scope.contracts[contract.name] = contract
elif refId in sol_parser.functions_by_id:
print("found in functions")
functions = sol_parser.functions_by_id[refId]
assert len(functions) == 1
function = functions[0]
scope.functions.add(function)
elif refId in sol_parser._imports_by_id:
import_directive = sol_parser._imports_by_id[refId]
scope.imports.add(import_directive)
elif refId in sol_parser._top_level_variables_by_id:
top_level_variable = sol_parser._top_level_variables_by_id[refId]
scope.variables[top_level_variable.name] = top_level_variable
elif refId in sol_parser._top_level_events_by_id:
top_level_event = sol_parser._top_level_events_by_id[refId]
scope.events.add(top_level_event)
elif refId in sol_parser._top_level_structures_by_id:
top_level_struct = sol_parser._top_level_structures_by_id[refId]
scope.structures[top_level_struct.name] = top_level_struct
elif refId in sol_parser._top_level_type_aliases_by_id:
top_level_type_alias = sol_parser._top_level_type_aliases_by_id[refId]
scope.type_aliases[top_level_type_alias.name] = top_level_type_alias
elif refId in sol_parser._top_level_enums_by_id:
top_level_enum = sol_parser._top_level_enums_by_id[refId]
scope.enums[top_level_enum.name] = top_level_enum
elif refId in sol_parser._top_level_errors_by_id:
print("found in errors")
top_level_custom_error = sol_parser._top_level_errors_by_id[refId]
print(top_level_custom_error.name)
scope.custom_errors.add(top_level_custom_error)
else:
print("not found", refId)
assert False
_update_file_scopes(sol_parser)
if kwargs.get("generate_patches", False):
self.generate_patches = True

@ -600,12 +600,6 @@ def propagate_types(ir: Operation, node: "Node"): # pylint: disable=too-many-lo
using_for = node_function.contract.using_for_complete
elif isinstance(node_function, FunctionTopLevel):
using_for = node_function.using_for_complete
# print("\n")
# print("using_for", )
# for key,v in using_for.items():
# print("key",key, )
# for i in v:
# print("value",i,i.__class__ )
if isinstance(ir, OperationWithLValue) and ir.lvalue:
# Force assignment in case of missing previous correct type
@ -668,7 +662,6 @@ def propagate_types(ir: Operation, node: "Node"): # pylint: disable=too-many-lo
ir, node_function.contract
)
if can_be_low_level(ir):
print("can be low level")
return convert_to_low_level(ir)
# Convert push operations
@ -1509,7 +1502,6 @@ def convert_to_pop(ir: HighLevelCall, node: "Node") -> List[Operation]:
def look_for_library_or_top_level(
contract: Contract,
ir: HighLevelCall,
using_for,
t: Union[
@ -1519,11 +1511,7 @@ def look_for_library_or_top_level(
TypeAliasTopLevel,
],
) -> Optional[Union[LibraryCall, InternalCall,]]:
print("look_for_library_or_top_level")
print(ir.expression.source_mapping.to_detailed_str())
print(ir.function_name)
for destination in using_for[t]:
print("destionation", destination, destination.__class__)
if isinstance(destination, FunctionTopLevel) and destination.name == ir.function_name:
arguments = [ir.destination] + ir.arguments
if (
@ -1566,7 +1554,6 @@ def look_for_library_or_top_level(
new_ir = convert_type_library_call(lib_call, lib_contract)
if new_ir:
new_ir.set_node(ir.node)
print("new_ir", new_ir)
return new_ir
return None
@ -1583,12 +1570,12 @@ def convert_to_library_or_top_level(
t = ir.destination.type
if t in using_for:
new_ir = look_for_library_or_top_level(contract, ir, using_for, t)
new_ir = look_for_library_or_top_level(ir, using_for, t)
if new_ir:
return new_ir
if "*" in using_for:
new_ir = look_for_library_or_top_level(contract, ir, using_for, "*")
new_ir = look_for_library_or_top_level(ir, using_for, "*")
if new_ir:
return new_ir
@ -1599,7 +1586,7 @@ def convert_to_library_or_top_level(
and UserDefinedType(node.function.contract) in using_for
):
new_ir = look_for_library_or_top_level(
contract, ir, using_for, UserDefinedType(node.function.contract)
ir, using_for, UserDefinedType(node.function.contract)
)
if new_ir:
return new_ir
@ -1754,9 +1741,6 @@ def convert_type_of_high_and_internal_level_call(
]
for import_statement in contract.file_scope.imports:
print(import_statement)
print(import_statement.alias)
print(ir.contract_name)
if (
import_statement.alias is not None
and import_statement.alias == ir.contract_name
@ -1961,7 +1945,6 @@ def convert_constant_types(irs: List[Operation]) -> None:
if isinstance(func, StateVariable):
types = export_nested_types_from_variable(func)
else:
print("func", func, ir.expression.source_mapping.to_detailed_str())
types = [p.type for p in func.parameters]
assert len(types) == len(ir.arguments)
for idx, arg in enumerate(ir.arguments):

@ -585,11 +585,6 @@ class ContractSolc(CallerContextExpression):
element.is_shadowed = True
accessible_elements[element.full_name].shadows = True
except (VariableNotFound, KeyError) as e:
for c in self._contract.inheritance:
print(c.name, c.id)
for c2 in c.inheritance:
print("\t", c2.name, c2.id)
print("\n")
self.log_incorrect_parsing(
f"Missing params {e} {self._contract.source_mapping.to_detailed_str()}"
)
@ -626,11 +621,6 @@ class ContractSolc(CallerContextExpression):
self._contract.using_for[type_name] = []
if "libraryName" in using_for:
# print(using_for["libraryName"])
# x =
# for f in x.type.functions:
# assert isinstance(f, Function), x.__class__
self._contract.using_for[type_name].append(
parse_type(using_for["libraryName"], self)
)
@ -649,7 +639,6 @@ class ContractSolc(CallerContextExpression):
old = "*"
if old not in self._contract.using_for:
self._contract.using_for[old] = []
self._contract.using_for[old].append(new)
self._usingForNotParsed = []
except (VariableNotFound, KeyError) as e:
@ -689,7 +678,6 @@ class ContractSolc(CallerContextExpression):
def _analyze_top_level_function(self, function_name: str, type_name: USING_FOR_KEY) -> None:
for tl_function in self.compilation_unit.functions_top_level:
if tl_function.name == function_name:
assert isinstance(tl_function, Function)
self._contract.using_for[type_name].append(tl_function)
def _analyze_library_function(
@ -703,7 +691,6 @@ class ContractSolc(CallerContextExpression):
if c.name == library_name:
for f in c.functions:
if f.name == function_name:
assert isinstance(f, FunctionContract)
self._contract.using_for[type_name].append(f)
found = True
break

@ -51,10 +51,7 @@ class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few-
if self._library_name:
library_name = parse_type(self._library_name, self)
assert isinstance(library_name, UserDefinedType)
# for f in library_name.type.functions:
self._using_for.using_for[type_name].append(library_name)
self._propagate_global(type_name)
else:
for f in self._functions:

@ -304,10 +304,8 @@ def _find_variable_init(
scope = underlying_function.file_scope
else:
assert isinstance(underlying_function, FunctionContract)
scope = underlying_function.file_scope
scope = underlying_function.contract.file_scope
# scope = underlying_function.file_scope
# assert False
elif isinstance(caller_context, StructureTopLevelSolc):
direct_contracts = []
direct_functions_parser = []
@ -463,8 +461,6 @@ def find_variable(
return all_enums[var_name], False
contracts = current_scope.contracts
# print(*contracts)
# print(var_name in contracts)
if var_name in contracts:
return contracts[var_name], False

@ -88,7 +88,6 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
self._top_level_errors_by_id: Dict[int, EventTopLevel] = {}
self._top_level_structures_by_id: Dict[int, StructureTopLevel] = {}
self._top_level_variables_by_id: Dict[int, TopLevelVariable] = {}
# self._top_level_using_for_by_id: Dict[int, UsingForTopLevel] = {}
self._top_level_type_aliases_by_id: Dict[int, TypeAliasTopLevel] = {}
self._top_level_enums_by_id: Dict[int, EnumTopLevel] = {}
@ -289,12 +288,6 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
self._compilation_unit.using_for_top_level.append(usingFor)
self._using_for_top_level_parser.append(usingFor_parser)
# print(top_level_data)
# if usingFor_parser.is_global:
# for f in usingFor_parser._functions:
# scope.ex
# referenceId = top_level_data["id"]
# self._top_level_using_for_by_id[referenceId] = usingFor
elif top_level_data[self.get_key()] == "ImportDirective":
referenceId = top_level_data["id"]
@ -841,19 +834,19 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
raise e
for func in self._compilation_unit.functions_top_level:
# try:
func.generate_slithir_and_analyze()
# except AttributeError as e:
# logger.error(
# f"Impossible to generate IR for top level function {func.name} ({func.source_mapping}):\n {e}"
# )
# except Exception as e:
# func_expressions = "\n".join([f"\t{ex}" for ex in func.expressions])
# logger.error(
# f"\nFailed to generate IR for top level function {func.name}. Please open an issue https://github.com/crytic/slither/issues.\n{func.name} ({func.source_mapping}):\n "
# f"{func_expressions}"
# )
# raise e
try:
func.generate_slithir_and_analyze()
except AttributeError as e:
logger.error(
f"Impossible to generate IR for top level function {func.name} ({func.source_mapping}):\n {e}"
)
except Exception as e:
func_expressions = "\n".join([f"\t{ex}" for ex in func.expressions])
logger.error(
f"\nFailed to generate IR for top level function {func.name}. Please open an issue https://github.com/crytic/slither/issues.\n{func.name} ({func.source_mapping}):\n "
f"{func_expressions}"
)
raise e
try:
func.generate_slithir_ssa({})

@ -238,7 +238,7 @@ def parse_type(
renaming: Dict[str, str]
type_aliases: Dict[str, TypeAlias]
enums_direct_access: List["Enum"] = []
# Note: for convenicence top level functions use the same parser than function in contract
# Note: for convenience top level functions use the same parser as function in contract
# but contract_parser is set to None
if isinstance(caller_context, SlitherCompilationUnitSolc) or (
isinstance(caller_context, FunctionSolc) and caller_context.contract_parser is None
@ -313,19 +313,17 @@ def parse_type(
sl = caller_context.compilation_unit
if isinstance(caller_context, FunctionSolc):
underlying_func = caller_context.underlying_function
# If contract_parser is set to None, then underlying_function is a functionContract
# If contract_parser is set to None, then underlying_function is a FunctionContract
# See note above
assert isinstance(underlying_func, FunctionContract)
contract = underlying_func.contract
next_context = caller_context.contract_parser
scope = caller_context.underlying_function.file_scope
scope = underlying_func.file_scope
else:
contract = caller_context.underlying_contract
next_context = caller_context
scope = caller_context.underlying_contract.file_scope
print("scope: ", scope)
print("contracts: ", *scope.contracts)
# For functions declared in this file the scope is the
scope = contract.file_scope
structures_direct_access = contract.structures
structures_direct_access += scope.structures.values()
all_structuress = [c.structures for c in scope.contracts.values()]
@ -337,7 +335,6 @@ def parse_type(
all_enums = [item for sublist in all_enumss for item in sublist]
all_enums += scope.enums.values()
contracts = scope.contracts.values()
# print("contracts: ", *contracts)
functions = contract.functions + contract.modifiers
renaming = scope.renaming

@ -645,10 +645,6 @@ class ExpressionToSlithIR(ExpressionVisitor):
return True
for custom_error in scope.custom_errors:
print("scope", scope)
print(*scope.exported_symbols)
print("custom", custom_error)
print(custom_error.name, elem)
if custom_error.name == elem:
set_val(expression, custom_error)
return True

@ -473,6 +473,7 @@ ALL_TESTS = [
Test("enum-max-min.sol", ["0.8.19"]),
Test("event-top-level.sol", ["0.8.22"]),
Test("solidity-0.8.24.sol", ["0.8.24"], solc_args="--evm-version cancun"),
Test("scope/inherited_function_scope.sol", ["0.8.24"]),
]
# create the output folder if needed
try:

@ -0,0 +1,12 @@
{
"B": {
"_a()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n}\n",
"b()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n",
"constructor()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
},
"A": {
"_a()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n}\n",
"b()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n"
},
"Z": {}
}

@ -0,0 +1,7 @@
import {A} from "./scope_a.sol";
contract B is A {
constructor() {
b();
}
}

@ -0,0 +1,9 @@
import {Z} from "./scope_z.sol";
contract A {
function _a() private returns (Z) {
}
function b() public {
_a();
}
}
Loading…
Cancel
Save