diff --git a/slither/core/scope/scope.py b/slither/core/scope/scope.py index 784b17cb20..99fe00b253 100644 --- a/slither/core/scope/scope.py +++ b/slither/core/scope/scope.py @@ -36,11 +36,11 @@ def __init__(self, filename: Filename) -> None: # So we simplify the logic and have the scope fields all populated self.custom_errors: Set[CustomErrorTopLevel] = set() self.enums: Dict[str, EnumTopLevel] = {} - self.events: Dict[str, EventTopLevel] = {} # Functions is a list instead of a dict # Because we parse the function signature later on # So we simplify the logic and have the scope fields all populated self.functions: Set[FunctionTopLevel] = set() + self.events: Set[EventTopLevel] = set() self.using_for_directives: Set[UsingForTopLevel] = set() self.imports: Set[Import] = set() self.pragmas: Set[Pragma] = set() @@ -76,8 +76,8 @@ def add_accesible_scopes(self) -> bool: # pylint: disable=too-many-branches if not _dict_contain(new_scope.enums, self.enums): self.enums.update(new_scope.enums) learn_something = True - if not _dict_contain(new_scope.events, self.events): - self.events.update(new_scope.events) + 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 diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index f0696d557b..b27cce7988 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -16,7 +16,7 @@ from slither.core.variables.state_variable import StateVariable from slither.solc_parsing.declarations.caller_context import CallerContextExpression from slither.solc_parsing.declarations.custom_error import CustomErrorSolc -from slither.solc_parsing.declarations.event import EventSolc +from slither.solc_parsing.declarations.event_contract import EventContractSolc from slither.solc_parsing.declarations.function import FunctionSolc from slither.solc_parsing.declarations.modifier import ModifierSolc from slither.solc_parsing.declarations.structure_contract import StructureContractSolc @@ -751,7 +751,7 @@ def analyze_events(self) -> None: event.set_contract(self._contract) event.set_offset(event_to_parse["src"], self._contract.compilation_unit) - event_parser = EventSolc(event, event_to_parse, self._slither_parser) # type: ignore + event_parser = EventContractSolc(event, event_to_parse, self) # type: ignore event_parser.analyze() # type: ignore self._contract.events_as_dict[event.full_name] = event except (VariableNotFound, KeyError) as e: diff --git a/slither/solc_parsing/declarations/event.py b/slither/solc_parsing/declarations/event_contract.py similarity index 73% rename from slither/solc_parsing/declarations/event.py rename to slither/solc_parsing/declarations/event_contract.py index 4a7d62389d..6af45ba527 100644 --- a/slither/solc_parsing/declarations/event.py +++ b/slither/solc_parsing/declarations/event_contract.py @@ -1,27 +1,27 @@ """ - Event module + EventContract module """ from typing import TYPE_CHECKING, Dict from slither.core.variables.event_variable import EventVariable from slither.solc_parsing.variables.event_variable import EventVariableSolc -from slither.core.declarations.event import Event +from slither.core.declarations.event_contract import EventContract if TYPE_CHECKING: - from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc + from slither.solc_parsing.declarations.contract import ContractSolc -class EventSolc: +class EventContractSolc: """ - Event class + EventContract class """ def __init__( - self, event: Event, event_data: Dict, slither_parser: "SlitherCompilationUnitSolc" + self, event: EventContract, event_data: Dict, contract_parser: "ContractSolc" ) -> None: self._event = event - self._slither_parser = slither_parser + self._contract_parser = contract_parser if self.is_compact_ast: self._event.name = event_data["name"] @@ -42,16 +42,16 @@ def __init__( @property def is_compact_ast(self) -> bool: - return self._slither_parser.is_compact_ast + return self._contract_parser.is_compact_ast def analyze(self) -> None: for elem_to_parse in self._elemsNotParsed: elem = EventVariable() # Todo: check if the source offset is always here if "src" in elem_to_parse: - elem.set_offset(elem_to_parse["src"], self._slither_parser.compilation_unit) + elem.set_offset(elem_to_parse["src"], self._contract_parser.compilation_unit) elem_parser = EventVariableSolc(elem, elem_to_parse) - elem_parser.analyze(self._slither_parser) + elem_parser.analyze(self._contract_parser) self._event.elems.append(elem) diff --git a/slither/solc_parsing/declarations/event_top_level.py b/slither/solc_parsing/declarations/event_top_level.py new file mode 100644 index 0000000000..9b6b676c79 --- /dev/null +++ b/slither/solc_parsing/declarations/event_top_level.py @@ -0,0 +1,75 @@ +""" + EventTopLevel module +""" +from typing import TYPE_CHECKING, Dict + +from slither.core.declarations.event_top_level import EventTopLevel +from slither.core.variables.event_variable import EventVariable +from slither.core.compilation_unit import SlitherCompilationUnit +from slither.solc_parsing.variables.event_variable import EventVariableSolc +from slither.solc_parsing.declarations.caller_context import CallerContextExpression + +if TYPE_CHECKING: + from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc + + +class EventTopLevelSolc(CallerContextExpression): + """ + EventTopLevel class + """ + + def __init__( + self, event: EventTopLevel, event_data: Dict, slither_parser: "SlitherCompilationUnitSolc" + ) -> None: + + self._event = event + self._slither_parser = slither_parser + + if self.is_compact_ast: + self._event.name = event_data["name"] + elems = event_data["parameters"] + assert elems["nodeType"] == "ParameterList" + self._elemsNotParsed = elems["parameters"] + else: + self._event.name = event_data["attributes"]["name"] + for elem in event_data["children"]: + # From Solidity 0.6.3 to 0.6.10 (included) + # Comment above a event might be added in the children + # of an event for the legacy ast + if elem["name"] == "ParameterList": + if "children" in elem: + self._elemsNotParsed = elem["children"] + else: + self._elemsNotParsed = [] + + def analyze(self) -> None: + for elem_to_parse in self._elemsNotParsed: + elem = EventVariable() + # Todo: check if the source offset is always here + if "src" in elem_to_parse: + elem.set_offset(elem_to_parse["src"], self._slither_parser.compilation_unit) + elem_parser = EventVariableSolc(elem, elem_to_parse) + elem_parser.analyze(self) + + self._event.elems.append(elem) + + self._elemsNotParsed = [] + + @property + def is_compact_ast(self) -> bool: + return self._slither_parser.is_compact_ast + + @property + def compilation_unit(self) -> SlitherCompilationUnit: + return self._slither_parser.compilation_unit + + def get_key(self) -> str: + return self._slither_parser.get_key() + + @property + def slither_parser(self) -> "SlitherCompilationUnitSolc": + return self._slither_parser + + @property + def underlying_event(self) -> EventTopLevel: + return self._event diff --git a/slither/solc_parsing/expressions/find_variable.py b/slither/solc_parsing/expressions/find_variable.py index e7fa995215..404bcdee5f 100644 --- a/slither/solc_parsing/expressions/find_variable.py +++ b/slither/solc_parsing/expressions/find_variable.py @@ -134,8 +134,9 @@ def find_top_level( if var_name in scope.enums: return scope.enums[var_name], False - if var_name in scope.events: - return scope.events[var_name], False + for event in scope.events: + if var_name == event.full_name: + return event, False for import_directive in scope.imports: if import_directive.alias == var_name: @@ -268,6 +269,7 @@ def _find_variable_init( from slither.solc_parsing.declarations.function import FunctionSolc from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc + from slither.solc_parsing.declarations.event_top_level import EventTopLevelSolc from slither.solc_parsing.declarations.custom_error import CustomErrorSolc direct_contracts: List[Contract] @@ -311,6 +313,10 @@ def _find_variable_init( direct_contracts = [] direct_functions_parser = [] scope = caller_context.underlying_variable.file_scope + elif isinstance(caller_context, EventTopLevelSolc): + direct_contracts = [] + direct_functions_parser = [] + scope = caller_context.underlying_event.file_scope elif isinstance(caller_context, CustomErrorSolc): if caller_context.contract_parser: direct_contracts = [caller_context.contract_parser.underlying_contract] diff --git a/slither/solc_parsing/slither_compilation_unit_solc.py b/slither/solc_parsing/slither_compilation_unit_solc.py index ee28a7bf5d..02d8307024 100644 --- a/slither/solc_parsing/slither_compilation_unit_solc.py +++ b/slither/solc_parsing/slither_compilation_unit_solc.py @@ -24,7 +24,7 @@ from slither.solc_parsing.declarations.contract import ContractSolc from slither.solc_parsing.declarations.custom_error import CustomErrorSolc from slither.solc_parsing.declarations.function import FunctionSolc -from slither.solc_parsing.declarations.event import EventSolc +from slither.solc_parsing.declarations.event_top_level import EventTopLevelSolc from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc from slither.solc_parsing.declarations.using_for_top_level import UsingForTopLevelSolc from slither.solc_parsing.exceptions import VariableNotFound @@ -90,6 +90,7 @@ def __init__(self, compilation_unit: SlitherCompilationUnit) -> None: self._variables_top_level_parser: List[TopLevelVariableSolc] = [] self._functions_top_level_parser: List[FunctionSolc] = [] self._using_for_top_level_parser: List[UsingForTopLevelSolc] = [] + self._events_top_level_parser: List[EventTopLevelSolc] = [] self._all_functions_and_modifier_parser: List[FunctionSolc] = [] self._top_level_contracts_counter = 0 @@ -353,9 +354,9 @@ def parse_top_level_items(self, data_loaded: Dict, filename: str) -> None: event = EventTopLevel(scope) event.set_offset(top_level_data["src"], self._compilation_unit) - event_parser = EventSolc(event, top_level_data, self) # type: ignore - event_parser.analyze() # type: ignore - scope.events[event.full_name] = event + event_parser = EventTopLevelSolc(event, top_level_data, self) # type: ignore + self._events_top_level_parser.append(event_parser) + scope.events.add(event) self._compilation_unit.events_top_level.append(event) else: @@ -612,6 +613,7 @@ def _analyze_second_part( self._analyze_top_level_variables() self._analyze_top_level_structures() + self._analyze_top_level_events() # Start with the contracts without inheritance # Analyze a contract only if all its fathers @@ -722,6 +724,13 @@ def _analyze_top_level_variables(self) -> None: except (VariableNotFound, KeyError) as e: raise SlitherException(f"Missing {e} during variable analyze") from e + def _analyze_top_level_events(self) -> None: + try: + for event in self._events_top_level_parser: + event.analyze() + except (VariableNotFound, KeyError) as e: + raise SlitherException(f"Missing event {e} during top level event analyze") from e + def _analyze_params_top_level_function(self) -> None: for func_parser in self._functions_top_level_parser: func_parser.analyze_params() diff --git a/slither/solc_parsing/solidity_types/type_parsing.py b/slither/solc_parsing/solidity_types/type_parsing.py index 82c1ac3926..06a91f9114 100644 --- a/slither/solc_parsing/solidity_types/type_parsing.py +++ b/slither/solc_parsing/solidity_types/type_parsing.py @@ -232,6 +232,7 @@ def parse_type( from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc + from slither.solc_parsing.declarations.event_top_level import EventTopLevelSolc sl: "SlitherCompilationUnit" renaming: Dict[str, str] @@ -266,7 +267,13 @@ def parse_type( functions = [] elif isinstance( caller_context, - (StructureTopLevelSolc, CustomErrorSolc, TopLevelVariableSolc, UsingForTopLevelSolc), + ( + StructureTopLevelSolc, + CustomErrorSolc, + TopLevelVariableSolc, + UsingForTopLevelSolc, + EventTopLevelSolc, + ), ): if isinstance(caller_context, StructureTopLevelSolc): scope = caller_context.underlying_structure.file_scope @@ -274,6 +281,8 @@ def parse_type( scope = caller_context.underlying_variable.file_scope elif isinstance(caller_context, UsingForTopLevelSolc): scope = caller_context.underlying_using_for.file_scope + elif isinstance(caller_context, EventTopLevelSolc): + scope = caller_context.underlying_event.file_scope else: assert isinstance(caller_context, CustomErrorSolc) custom_error = caller_context.underlying_custom_error diff --git a/tests/e2e/solc_parsing/test_data/compile/event-top-level.sol-0.8.22-compact.zip b/tests/e2e/solc_parsing/test_data/compile/event-top-level.sol-0.8.22-compact.zip index ed82f32b17..4845562904 100644 Binary files a/tests/e2e/solc_parsing/test_data/compile/event-top-level.sol-0.8.22-compact.zip and b/tests/e2e/solc_parsing/test_data/compile/event-top-level.sol-0.8.22-compact.zip differ diff --git a/tests/e2e/solc_parsing/test_data/event-top-level.sol b/tests/e2e/solc_parsing/test_data/event-top-level.sol index fa64e1bf7c..81ac94f7b4 100644 --- a/tests/e2e/solc_parsing/test_data/event-top-level.sol +++ b/tests/e2e/solc_parsing/test_data/event-top-level.sol @@ -1,7 +1,17 @@ event MyEvent(uint256 a); +uint256 constant A = 3; +event MyEvent2(uint8[A]); + contract T { + type MyType is uint256; + event MyCustomEvent(MyType mytype); + function a() public { emit MyEvent(2); } + + function b() public { + emit MyEvent2([1,2,3]); + } } diff --git a/tests/e2e/solc_parsing/test_data/expected/event-top-level.sol-0.8.22-compact.json b/tests/e2e/solc_parsing/test_data/expected/event-top-level.sol-0.8.22-compact.json index 58c6a3ab60..68dff297fb 100644 --- a/tests/e2e/solc_parsing/test_data/expected/event-top-level.sol-0.8.22-compact.json +++ b/tests/e2e/solc_parsing/test_data/expected/event-top-level.sol-0.8.22-compact.json @@ -1,5 +1,6 @@ { "T": { - "a()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" + "a()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n", + "b()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n}\n" } } \ No newline at end of file