Improve performances of offsets references.

pull/2481/head
Alexis 6 months ago
parent 02df0dcf37
commit ecca8da74e
No known key found for this signature in database
  1. 134
      slither/core/slither_core.py
  2. 6
      slither/core/source_mapping/source_mapping.py

@ -8,7 +8,7 @@ import pathlib
import posixpath import posixpath
import re import re
from collections import defaultdict from collections import defaultdict
from typing import Optional, Dict, List, Set, Union, Tuple from typing import Optional, Dict, List, Set, Union, Tuple, TypeVar
from crytic_compile import CryticCompile from crytic_compile import CryticCompile
from crytic_compile.utils.naming import Filename from crytic_compile.utils.naming import Filename
@ -88,6 +88,7 @@ class SlitherCore(Context):
self._contracts: List[Contract] = [] self._contracts: List[Contract] = []
self._contracts_derived: List[Contract] = [] self._contracts_derived: List[Contract] = []
self._offset_to_min_offset: Optional[Dict[Filename, Dict[int, Set[int]]]] = None
self._offset_to_objects: Optional[Dict[Filename, Dict[int, Set[SourceMapping]]]] = None self._offset_to_objects: Optional[Dict[Filename, Dict[int, Set[SourceMapping]]]] = None
self._offset_to_references: Optional[Dict[Filename, Dict[int, Set[Source]]]] = None self._offset_to_references: Optional[Dict[Filename, Dict[int, Set[Source]]]] = None
self._offset_to_implementations: Optional[Dict[Filename, Dict[int, Set[Source]]]] = None self._offset_to_implementations: Optional[Dict[Filename, Dict[int, Set[Source]]]] = None
@ -195,69 +196,70 @@ class SlitherCore(Context):
for f in c.functions: for f in c.functions:
f.cfg_to_dot(os.path.join(d, f"{c.name}.{f.name}.dot")) f.cfg_to_dot(os.path.join(d, f"{c.name}.{f.name}.dot"))
def offset_to_objects(self, filename_str: str, offset: int) -> Set[SourceMapping]:
if self._offset_to_objects is None:
self._compute_offsets_to_ref_impl_decl()
filename: Filename = self.crytic_compile.filename_lookup(filename_str)
return self._offset_to_objects[filename][offset]
def _compute_offsets_from_thing(self, thing: SourceMapping): def _compute_offsets_from_thing(self, thing: SourceMapping):
definition = get_definition(thing, self.crytic_compile) definition = get_definition(thing, self.crytic_compile)
references = get_references(thing) references = get_references(thing)
implementations = get_all_implementations(thing, self.contracts) implementations = get_all_implementations(thing, self.contracts)
# Create the offset mapping
for offset in range(definition.start, definition.end + 1): for offset in range(definition.start, definition.end + 1):
if ( self._offset_to_min_offset[definition.filename][offset].add(definition.start)
isinstance(thing, (TopLevel, Contract))
or (
isinstance(thing, FunctionContract)
and thing.contract_declarer == thing.contract
)
or (isinstance(thing, ContractLevel) and not isinstance(thing, FunctionContract))
):
self._offset_to_objects[definition.filename][offset].add(thing) is_declared_function = (
isinstance(thing, FunctionContract) and thing.contract_declarer == thing.contract
)
self._offset_to_definitions[definition.filename][offset].add(definition) should_add_to_objects = (
self._offset_to_implementations[definition.filename][offset].update(implementations) isinstance(thing, (TopLevel, Contract))
self._offset_to_references[definition.filename][offset] |= set(references) or is_declared_function
or (isinstance(thing, ContractLevel) and not isinstance(thing, FunctionContract))
)
if should_add_to_objects:
self._offset_to_objects[definition.filename][definition.start].add(thing)
self._offset_to_definitions[definition.filename][definition.start].add(definition)
self._offset_to_implementations[definition.filename][definition.start].update(
implementations
)
self._offset_to_references[definition.filename][definition.start] |= set(references)
# For references
should_add_to_objects = (
isinstance(thing, TopLevel)
or is_declared_function
or (isinstance(thing, ContractLevel) and not isinstance(thing, FunctionContract))
)
for ref in references: for ref in references:
for offset in range(ref.start, ref.end + 1): for offset in range(ref.start, ref.end + 1):
is_declared_function = ( self._offset_to_min_offset[definition.filename][offset].add(ref.start)
isinstance(thing, FunctionContract)
and thing.contract_declarer == thing.contract if should_add_to_objects:
) self._offset_to_objects[definition.filename][ref.start].add(thing)
if is_declared_function:
# Only show the nearest lexical definition for declared contract-level functions
if ( if (
isinstance(thing, TopLevel) thing.contract.source_mapping.start
or is_declared_function < ref.start
or ( < thing.contract.source_mapping.end
isinstance(thing, ContractLevel) and not isinstance(thing, FunctionContract)
)
): ):
self._offset_to_objects[definition.filename][offset].add(thing)
if is_declared_function:
# Only show the nearest lexical definition for declared contract-level functions
if (
thing.contract.source_mapping.start
< offset
< thing.contract.source_mapping.end
):
self._offset_to_definitions[ref.filename][offset].add(definition) self._offset_to_definitions[ref.filename][ref.start].add(definition)
else: else:
self._offset_to_definitions[ref.filename][offset].add(definition) self._offset_to_definitions[ref.filename][ref.start].add(definition)
self._offset_to_implementations[ref.filename][offset].update(implementations) self._offset_to_implementations[ref.filename][ref.start].update(implementations)
self._offset_to_references[ref.filename][offset] |= set(references) self._offset_to_references[ref.filename][ref.start] |= set(references)
def _compute_offsets_to_ref_impl_decl(self): # pylint: disable=too-many-branches def _compute_offsets_to_ref_impl_decl(self): # pylint: disable=too-many-branches
self._offset_to_references = defaultdict(lambda: defaultdict(lambda: set())) self._offset_to_references = defaultdict(lambda: defaultdict(lambda: set()))
self._offset_to_definitions = defaultdict(lambda: defaultdict(lambda: set())) self._offset_to_definitions = defaultdict(lambda: defaultdict(lambda: set()))
self._offset_to_implementations = defaultdict(lambda: defaultdict(lambda: set())) self._offset_to_implementations = defaultdict(lambda: defaultdict(lambda: set()))
self._offset_to_objects = defaultdict(lambda: defaultdict(lambda: set())) self._offset_to_objects = defaultdict(lambda: defaultdict(lambda: set()))
self._offset_to_min_offset = defaultdict(lambda: defaultdict(lambda: set()))
for compilation_unit in self._compilation_units: for compilation_unit in self._compilation_units:
for contract in compilation_unit.contracts: for contract in compilation_unit.contracts:
@ -308,23 +310,59 @@ class SlitherCore(Context):
for pragma in compilation_unit.pragma_directives: for pragma in compilation_unit.pragma_directives:
self._compute_offsets_from_thing(pragma) self._compute_offsets_from_thing(pragma)
T = TypeVar("T", Source, SourceMapping)
def _get_offset(
self, mapping: Dict[Filename, Dict[int, Set[T]]], filename_str: str, offset: int
) -> Set[T]:
"""Get the Source/SourceMapping referenced by the offset.
For performance reasons, references are only stored once at the lowest offset.
It uses the _offset_to_min_offset mapping to retrieve the correct offsets.
As multiple definitions can be related to the same offset, we retrieve all of them.
:param mapping: Mapping to search for (objects. references, ...)
:param filename_str: Filename to consider
:param offset: Look-up offset
:raises IndexError: When the start offset is not found
:return: The corresponding set of Source/SourceMapping
"""
filename: Filename = self.crytic_compile.filename_lookup(filename_str)
start_offsets = self._offset_to_min_offset[filename][offset]
if not start_offsets:
msg = f"Unable to find reference for offset {offset}"
raise IndexError(msg)
results = set()
for start_offset in start_offsets:
results |= mapping[filename][start_offset]
return results
def offset_to_references(self, filename_str: str, offset: int) -> Set[Source]: def offset_to_references(self, filename_str: str, offset: int) -> Set[Source]:
if self._offset_to_references is None: if self._offset_to_references is None:
self._compute_offsets_to_ref_impl_decl() self._compute_offsets_to_ref_impl_decl()
filename: Filename = self.crytic_compile.filename_lookup(filename_str)
return self._offset_to_references[filename][offset] return self._get_offset(self._offset_to_references, filename_str, offset)
def offset_to_implementations(self, filename_str: str, offset: int) -> Set[Source]: def offset_to_implementations(self, filename_str: str, offset: int) -> Set[Source]:
if self._offset_to_implementations is None: if self._offset_to_implementations is None:
self._compute_offsets_to_ref_impl_decl() self._compute_offsets_to_ref_impl_decl()
filename: Filename = self.crytic_compile.filename_lookup(filename_str)
return self._offset_to_implementations[filename][offset] return self._get_offset(self._offset_to_implementations, filename_str, offset)
def offset_to_definitions(self, filename_str: str, offset: int) -> Set[Source]: def offset_to_definitions(self, filename_str: str, offset: int) -> Set[Source]:
if self._offset_to_definitions is None: if self._offset_to_definitions is None:
self._compute_offsets_to_ref_impl_decl() self._compute_offsets_to_ref_impl_decl()
filename: Filename = self.crytic_compile.filename_lookup(filename_str)
return self._offset_to_definitions[filename][offset] return self._get_offset(self._offset_to_definitions, filename_str, offset)
def offset_to_objects(self, filename_str: str, offset: int) -> Set[SourceMapping]:
if self._offset_to_objects is None:
self._compute_offsets_to_ref_impl_decl()
return self._get_offset(self._offset_to_objects, filename_str, offset)
# endregion # endregion
################################################################################### ###################################################################################

@ -112,12 +112,8 @@ class Source:
try: try:
return ( return (
self.start == other.start self.start == other.start
and self.length == other.length and self.filename.relative == other.filename.relative
and self.filename == other.filename
and self.is_dependency == other.is_dependency and self.is_dependency == other.is_dependency
and self.lines == other.lines
and self.starting_column == other.starting_column
and self.ending_column == other.ending_column
and self.end == other.end and self.end == other.end
) )
except AttributeError: except AttributeError:

Loading…
Cancel
Save