From 4a3354173818fb4cdf52f6b0aa62df60535fb903 Mon Sep 17 00:00:00 2001 From: Simone Date: Mon, 6 Nov 2023 19:07:01 +0100 Subject: [PATCH] Add support top level events --- slither/core/compilation_unit.py | 6 +++++ slither/core/declarations/__init__.py | 2 ++ slither/core/declarations/contract.py | 12 ++++----- slither/core/declarations/event.py | 24 ++--------------- slither/core/declarations/event_contract.py | 25 ++++++++++++++++++ slither/core/declarations/event_top_level.py | 13 +++++++++ slither/core/scope/scope.py | 7 ++++- slither/core/slither_core.py | 2 ++ slither/solc_parsing/declarations/contract.py | 8 +++--- slither/solc_parsing/declarations/event.py | 19 +++++++------ .../solc_parsing/expressions/find_variable.py | 3 +++ .../slither_compilation_unit_solc.py | 11 ++++++++ slither/tools/flattening/flattening.py | 1 + .../vyper_parsing/declarations/contract.py | 4 +-- tests/e2e/solc_parsing/test_ast_parsing.py | 1 + .../event-top-level.sol-0.8.22-compact.zip | Bin 0 -> 2022 bytes .../test_data/event-top-level.sol | 7 +++++ .../event-top-level.sol-0.8.22-compact.json | 5 ++++ 18 files changed, 105 insertions(+), 45 deletions(-) create mode 100644 slither/core/declarations/event_contract.py create mode 100644 slither/core/declarations/event_top_level.py create mode 100644 tests/e2e/solc_parsing/test_data/compile/event-top-level.sol-0.8.22-compact.zip create mode 100644 tests/e2e/solc_parsing/test_data/event-top-level.sol create mode 100644 tests/e2e/solc_parsing/test_data/expected/event-top-level.sol-0.8.22-compact.json diff --git a/slither/core/compilation_unit.py b/slither/core/compilation_unit.py index 87853aa34..6221fd8bd 100644 --- a/slither/core/compilation_unit.py +++ b/slither/core/compilation_unit.py @@ -16,6 +16,7 @@ from slither.core.declarations import ( ) from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel from slither.core.declarations.enum_top_level import EnumTopLevel +from slither.core.declarations.event_top_level import EventTopLevel from slither.core.declarations.function_top_level import FunctionTopLevel from slither.core.declarations.structure_top_level import StructureTopLevel from slither.core.declarations.using_for_top_level import UsingForTopLevel @@ -57,6 +58,7 @@ class SlitherCompilationUnit(Context): self.contracts: List[Contract] = [] self._structures_top_level: List[StructureTopLevel] = [] self._enums_top_level: List[EnumTopLevel] = [] + self._events_top_level: List[EventTopLevel] = [] self._variables_top_level: List[TopLevelVariable] = [] self._functions_top_level: List[FunctionTopLevel] = [] self._using_for_top_level: List[UsingForTopLevel] = [] @@ -234,6 +236,10 @@ class SlitherCompilationUnit(Context): def enums_top_level(self) -> List[EnumTopLevel]: return self._enums_top_level + @property + def events_top_level(self) -> List[EventTopLevel]: + return self._events_top_level + @property def variables_top_level(self) -> List[TopLevelVariable]: return self._variables_top_level diff --git a/slither/core/declarations/__init__.py b/slither/core/declarations/__init__.py index f6e902e06..9d727da8e 100644 --- a/slither/core/declarations/__init__.py +++ b/slither/core/declarations/__init__.py @@ -1,6 +1,8 @@ from .contract import Contract from .enum import Enum from .event import Event +from .event_contract import EventContract +from .event_top_level import EventTopLevel from .function import Function from .import_directive import Import from .modifier import Modifier diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index 458f951f5..5403d227a 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -33,7 +33,7 @@ if TYPE_CHECKING: from slither.utils.type_helpers import LibraryCallType, HighLevelCallType, InternalCallType from slither.core.declarations import ( Enum, - Event, + EventContract, Modifier, EnumContract, StructureContract, @@ -73,7 +73,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods self._enums: Dict[str, "EnumContract"] = {} self._structures: Dict[str, "StructureContract"] = {} - self._events: Dict[str, "Event"] = {} + self._events: Dict[str, "EventContract"] = {} # map accessible variable from name -> variable # do not contain private variables inherited from contract self._variables: Dict[str, "StateVariable"] = {} @@ -278,28 +278,28 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods ################################################################################### @property - def events(self) -> List["Event"]: + def events(self) -> List["EventContract"]: """ list(Event): List of the events """ return list(self._events.values()) @property - def events_inherited(self) -> List["Event"]: + def events_inherited(self) -> List["EventContract"]: """ list(Event): List of the inherited events """ return [e for e in self.events if e.contract != self] @property - def events_declared(self) -> List["Event"]: + def events_declared(self) -> List["EventContract"]: """ list(Event): List of the events declared within the contract (not inherited) """ return [e for e in self.events if e.contract == self] @property - def events_as_dict(self) -> Dict[str, "Event"]: + def events_as_dict(self) -> Dict[str, "EventContract"]: return self._events # endregion diff --git a/slither/core/declarations/event.py b/slither/core/declarations/event.py index 1b58ff63b..7149e20c0 100644 --- a/slither/core/declarations/event.py +++ b/slither/core/declarations/event.py @@ -1,14 +1,10 @@ -from typing import List, Tuple, TYPE_CHECKING +from typing import List, Tuple -from slither.core.declarations.contract_level import ContractLevel from slither.core.source_mapping.source_mapping import SourceMapping from slither.core.variables.event_variable import EventVariable -if TYPE_CHECKING: - from slither.core.declarations import Contract - -class Event(ContractLevel, SourceMapping): +class Event(SourceMapping): def __init__(self) -> None: super().__init__() self._name = None @@ -39,25 +35,9 @@ class Event(ContractLevel, SourceMapping): name, parameters = self.signature return name + "(" + ",".join(parameters) + ")" - @property - def canonical_name(self) -> str: - """Return the function signature as a str - Returns: - str: contract.func_name(type1,type2) - """ - return self.contract.name + "." + self.full_name - @property def elems(self) -> List["EventVariable"]: return self._elems - def is_declared_by(self, contract: "Contract") -> bool: - """ - Check if the element is declared by the contract - :param contract: - :return: - """ - return self.contract == contract - def __str__(self) -> str: return self.name diff --git a/slither/core/declarations/event_contract.py b/slither/core/declarations/event_contract.py new file mode 100644 index 000000000..652f8ca20 --- /dev/null +++ b/slither/core/declarations/event_contract.py @@ -0,0 +1,25 @@ +from typing import TYPE_CHECKING + +from slither.core.declarations.contract_level import ContractLevel +from slither.core.declarations import Event + +if TYPE_CHECKING: + from slither.core.declarations import Contract + + +class EventContract(Event, ContractLevel): + def is_declared_by(self, contract: "Contract") -> bool: + """ + Check if the element is declared by the contract + :param contract: + :return: + """ + return self.contract == contract + + @property + def canonical_name(self) -> str: + """Return the function signature as a str + Returns: + str: contract.func_name(type1,type2) + """ + return self.contract.name + "." + self.full_name diff --git a/slither/core/declarations/event_top_level.py b/slither/core/declarations/event_top_level.py new file mode 100644 index 000000000..5f9f1774d --- /dev/null +++ b/slither/core/declarations/event_top_level.py @@ -0,0 +1,13 @@ +from typing import TYPE_CHECKING + +from slither.core.declarations import Event +from slither.core.declarations.top_level import TopLevel + +if TYPE_CHECKING: + from slither.core.scope.scope import FileScope + + +class EventTopLevel(Event, TopLevel): + def __init__(self, scope: "FileScope") -> None: + super().__init__() + self.file_scope: "FileScope" = scope diff --git a/slither/core/scope/scope.py b/slither/core/scope/scope.py index 937a05136..784b17cb2 100644 --- a/slither/core/scope/scope.py +++ b/slither/core/scope/scope.py @@ -7,6 +7,7 @@ from crytic_compile.utils.naming import Filename from slither.core.declarations import Contract, Import, Pragma from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel from slither.core.declarations.enum_top_level import EnumTopLevel +from slither.core.declarations.event_top_level import EventTopLevel from slither.core.declarations.function_top_level import FunctionTopLevel from slither.core.declarations.using_for_top_level import UsingForTopLevel from slither.core.declarations.structure_top_level import StructureTopLevel @@ -35,6 +36,7 @@ class FileScope: # 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 @@ -54,7 +56,7 @@ class FileScope: # Name -> type alias self.type_aliases: Dict[str, TypeAlias] = {} - def add_accesible_scopes(self) -> bool: + def add_accesible_scopes(self) -> bool: # pylint: disable=too-many-branches """ Add information from accessible scopes. Return true if new information was obtained @@ -74,6 +76,9 @@ class FileScope: 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) + learn_something = True if not new_scope.functions.issubset(self.functions): self.functions |= new_scope.functions learn_something = True diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index 3535f2604..b1bb7c66b 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -269,6 +269,8 @@ class SlitherCore(Context): self._compute_offsets_from_thing(event) for enum in compilation_unit.enums_top_level: self._compute_offsets_from_thing(enum) + for event in compilation_unit.events_top_level: + self._compute_offsets_from_thing(event) for function in compilation_unit.functions_top_level: self._compute_offsets_from_thing(function) for st in compilation_unit.structures_top_level: diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index 3dd6e2fd5..34e2f2430 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -4,7 +4,7 @@ from typing import Any, List, Dict, Callable, TYPE_CHECKING, Union, Set, Sequenc from slither.core.declarations import ( Modifier, - Event, + EventContract, EnumContract, StructureContract, Function, @@ -747,12 +747,12 @@ class ContractSolc(CallerContextExpression): self._contract.events_as_dict.update(father.events_as_dict) for event_to_parse in self._eventsNotParsed: - event = Event() + event = EventContract() event.set_contract(self._contract) event.set_offset(event_to_parse["src"], self._contract.compilation_unit) - event_parser = EventSolc(event, event_to_parse, self) # type: ignore - event_parser.analyze(self) # type: ignore + event_parser = EventSolc(event, event_to_parse, self._slither_parser) # type: ignore + event_parser.analyze() # type: ignore self._contract.events_as_dict[event.full_name] = event except (VariableNotFound, KeyError) as e: self.log_incorrect_parsing(f"Missing event {e}") diff --git a/slither/solc_parsing/declarations/event.py b/slither/solc_parsing/declarations/event.py index 6531e6536..4a7d62389 100644 --- a/slither/solc_parsing/declarations/event.py +++ b/slither/solc_parsing/declarations/event.py @@ -8,7 +8,7 @@ from slither.solc_parsing.variables.event_variable import EventVariableSolc from slither.core.declarations.event import Event if TYPE_CHECKING: - from slither.solc_parsing.declarations.contract import ContractSolc + from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc class EventSolc: @@ -16,11 +16,12 @@ class EventSolc: Event class """ - def __init__(self, event: Event, event_data: Dict, contract_parser: "ContractSolc") -> None: + def __init__( + self, event: Event, event_data: Dict, slither_parser: "SlitherCompilationUnitSolc" + ) -> None: self._event = event - event.set_contract(contract_parser.underlying_contract) - self._parser_contract = contract_parser + self._slither_parser = slither_parser if self.is_compact_ast: self._event.name = event_data["name"] @@ -41,18 +42,16 @@ class EventSolc: @property def is_compact_ast(self) -> bool: - return self._parser_contract.is_compact_ast + return self._slither_parser.is_compact_ast - def analyze(self, contract: "ContractSolc") -> None: + 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._parser_contract.underlying_contract.compilation_unit - ) + elem.set_offset(elem_to_parse["src"], self._slither_parser.compilation_unit) elem_parser = EventVariableSolc(elem, elem_to_parse) - elem_parser.analyze(contract) + elem_parser.analyze(self._slither_parser) self._event.elems.append(elem) diff --git a/slither/solc_parsing/expressions/find_variable.py b/slither/solc_parsing/expressions/find_variable.py index 2261350b4..e7fa99521 100644 --- a/slither/solc_parsing/expressions/find_variable.py +++ b/slither/solc_parsing/expressions/find_variable.py @@ -134,6 +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 import_directive in scope.imports: if import_directive.alias == var_name: new_val = SolidityImportPlaceHolder(import_directive) diff --git a/slither/solc_parsing/slither_compilation_unit_solc.py b/slither/solc_parsing/slither_compilation_unit_solc.py index 85921ce74..08ee62d80 100644 --- a/slither/solc_parsing/slither_compilation_unit_solc.py +++ b/slither/solc_parsing/slither_compilation_unit_solc.py @@ -10,6 +10,7 @@ from slither.core.compilation_unit import SlitherCompilationUnit from slither.core.declarations import Contract from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel from slither.core.declarations.enum_top_level import EnumTopLevel +from slither.core.declarations.event_top_level import EventTopLevel from slither.core.declarations.function_top_level import FunctionTopLevel from slither.core.declarations.import_directive import Import from slither.core.declarations.pragma_directive import Pragma @@ -23,6 +24,7 @@ from slither.solc_parsing.declarations.caller_context import CallerContextExpres 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.structure_top_level import StructureTopLevelSolc from slither.solc_parsing.declarations.using_for_top_level import UsingForTopLevelSolc from slither.solc_parsing.exceptions import VariableNotFound @@ -347,6 +349,15 @@ class SlitherCompilationUnitSolc(CallerContextExpression): self._compilation_unit.type_aliases[alias] = type_alias scope.type_aliases[alias] = type_alias + elif top_level_data[self.get_key()] == "EventDefinition": + 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 + self._compilation_unit.events_top_level.append(event) + else: raise SlitherException(f"Top level {top_level_data[self.get_key()]} not supported") diff --git a/slither/tools/flattening/flattening.py b/slither/tools/flattening/flattening.py index 55e1af21d..9cb2abc3f 100644 --- a/slither/tools/flattening/flattening.py +++ b/slither/tools/flattening/flattening.py @@ -77,6 +77,7 @@ class Flattening: self._get_source_code_top_level(compilation_unit.structures_top_level) self._get_source_code_top_level(compilation_unit.enums_top_level) + self._get_source_code_top_level(compilation_unit.events_top_level) self._get_source_code_top_level(compilation_unit.custom_errors) self._get_source_code_top_level(compilation_unit.variables_top_level) self._get_source_code_top_level(compilation_unit.functions_top_level) diff --git a/slither/vyper_parsing/declarations/contract.py b/slither/vyper_parsing/declarations/contract.py index 6ca9c6557..2acd43e0f 100644 --- a/slither/vyper_parsing/declarations/contract.py +++ b/slither/vyper_parsing/declarations/contract.py @@ -24,7 +24,7 @@ from slither.vyper_parsing.declarations.struct import StructVyper from slither.vyper_parsing.variables.state_variable import StateVariableVyper from slither.vyper_parsing.declarations.function import FunctionVyper from slither.core.declarations.function_contract import FunctionContract -from slither.core.declarations import Contract, StructureContract, EnumContract, Event +from slither.core.declarations import Contract, StructureContract, EnumContract, EventContract from slither.core.variables.state_variable import StateVariable @@ -478,7 +478,7 @@ class ContractVyper: # pylint: disable=too-many-instance-attributes def parse_events(self) -> None: for event_to_parse in self._eventsNotParsed: - event = Event() + event = EventContract() event.set_contract(self._contract) event.set_offset(event_to_parse.src, self._contract.compilation_unit) diff --git a/tests/e2e/solc_parsing/test_ast_parsing.py b/tests/e2e/solc_parsing/test_ast_parsing.py index bc57dc51b..61250c5af 100644 --- a/tests/e2e/solc_parsing/test_ast_parsing.py +++ b/tests/e2e/solc_parsing/test_ast_parsing.py @@ -462,6 +462,7 @@ ALL_TESTS = [ Test("aliasing/main.sol", ["0.8.19"]), Test("type-aliases.sol", ["0.8.19"]), Test("enum-max-min.sol", ["0.8.19"]), + Test("event-top-level.sol", ["0.8.22"]), ] # create the output folder if needed try: 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 new file mode 100644 index 0000000000000000000000000000000000000000..ed82f32b17350e7c70788a8d84e0d02dcbdd0bba GIT binary patch literal 2022 zcma)-c{~#g1IIUHBvRzQkBl7kxX+C!SMey@=wMb@=4#_*Dw2uHk;r1sZ02mt$K)J^ zJaXo~>SbcC=2}zX>3RQuKcDCK`{Vojfd zXaN9#+mW|JaC*4VFg?ugiP6V~Vtj5#g@xY5`5^D20@0DT_3vOqLxcs60B!&PaR7jI zQc{57FT?Pn;r@C{QBr3JTC+`wkm4a{qdbmnkvg|~-X?Wg9(iJ6v0+ODmY=rkD7Q^B z@Yo<`JC=%1j^M)cLWElT-iH$~#WgRhfD>{y@wY5PR!$A6W#mR@5x?!{3n$Pf(q(ae zFZxWjJ0JXY?vd!jL!t`1j6Q;6GHAVqfy@SWXC{%qVRL7K^&AmqPH%+aRUZ6$HhQ-! z0#!ZnV@g86a;9kisRHacvMt$8YPz;?{yk@?hW)NV1X@V$ZU!d4i}dLidRv2t8xF%m z&~W{*KfzG;#CLnr73^S76S*1~Up7H`;XP$TZh|9(lhGq=kMO%%RsIZNy$Ck=VUkQ0 z@n$UiG=_O}j8U_?n2Z0;+EuD~w{5hrrzB+Dg8Evo&QM>y=AA?##NzJASxl2m``V$g z)Q5}Rm0jj<-FInXD=tj68+{eM$_}}@?i0Q{f{)&x)za?DoE3_@NPrfaUCNS-%wvjd z>Udv2@99SwJCY+rcaPna9I)oea`AjIYnqX-tHUc8!u?I4*RDT-*03VMijMhG9XWyF zjP6+Hn{6|9zB#3*n9VIsqsPK87WO zN|*I?x145$+l`7Ye^Ge4Y)6S3H)rT_6?t(g^s2xaz8xJH(rtmI2-z=cR3@5D;Jdsb@dk+>2veK1M68klnfl-FkQ&Oc9La`GDCS4>n2@ z*pMJ-R9~U)kp?K4Lzc@KpHGJVT+9#_bzz^17MM4L`fY{9Hg`(XqM||fwy-|U5$tH| zf)OwHF;X&T&$ae1h@-<0`b5%aP<$Dii}{*SnWo5DA@;j~NM_`0Y) z%4h9qHtT?{8Wt?aCbxF-*NfmrJ%Zo#?}{eIwxoRVag8hg zqm|Jtx35W?|B$NaS}h6=KaqEQ)M3B(eErpeH``zht4U}($)L~d^Tk^4m(EYCmrdt) z((S0|I;e5BqTyKwN{Q(e1xI%oLSX~s`0`}L!i3nmvgL(N3w(rp>ENVAm=eFUmK{6@ z?KR}DEpy$8^GI5|72J5#3zof`)}h`bIjZVdSn#C^%XBv_Ji4?Iz36yr(KarNx8)Tv ztRfSC8n)eFUdJ1{29dfN)68dCkV;;=XRj%Fp6eVB)kF_zy!{!$5OeP-OelCY{ z%>j<`y)XozR&dekLNE#4nIY3qcj#0L%h8E4f)rQ=!}cisL@-lbL@S_M*`sb z>3)VO{p%b=m)c7|2jhkbG7P>EC;kjq9R3V2>e!RBQvS7^!nbtMPU+px)GRJ{|1!Z0 z4UN0bD+L9uzl|58#(_JzOPC~7HjW@3QB1sIf=Xj|2ymu;J}F)_y}_o7^!MPaWsSe96{95a5R z`5pFM$H-Rp1$~_ciG77b{e< z-6*P8?qynzlHZhePUxkpDpLsqxl!mAkjXQT5_dc~c>M{!^HgeQ#RZL+w4&13ry9C9&cY$YpFh@5{i+eq(;T1~N-!^45| zi>&pP3h%|~0l$vG|B{3UoKh=TO#Sxm{srI(wm7BppwbJ0v0|HD9wVD8aofC2N#oH> zpoZG6h?wfx^IGq`aw5{fb_io!27jt$RY;Ehj`yTvY00EydFu!8Uch^MxUq4KEiVnr zAP3Q%x@W6AZv^59_8JKu+-?S0GsV7({UG6t>QJRDhd0=i9RAkd`8Coj!Fr$QORa?@ zYm4cx$8uh#3R0`orJ@FjFP^{d)+^FbK+zY(50ZT_yUaT|!!+WwsG#Ah*0^e>sXigGw(QRz4Q?Dr+TD6*Z8DpEH~` p-Cclu5dQx!-{1cIU*drO;9siP-Q|ejKX>5odj4k2Z<_-Ee*>9z)$#xU literal 0 HcmV?d00001 diff --git a/tests/e2e/solc_parsing/test_data/event-top-level.sol b/tests/e2e/solc_parsing/test_data/event-top-level.sol new file mode 100644 index 000000000..fa64e1bf7 --- /dev/null +++ b/tests/e2e/solc_parsing/test_data/event-top-level.sol @@ -0,0 +1,7 @@ +event MyEvent(uint256 a); + +contract T { + function a() public { + emit MyEvent(2); + } +} 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 new file mode 100644 index 000000000..58c6a3ab6 --- /dev/null +++ b/tests/e2e/solc_parsing/test_data/expected/event-top-level.sol-0.8.22-compact.json @@ -0,0 +1,5 @@ +{ + "T": { + "a()": "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