slither.solc_parsing.slither_compilation_unit_solc

  1from collections import defaultdict
  2import json
  3import logging
  4import os
  5import re
  6from pathlib import Path
  7
  8from slither.analyses.data_dependency.data_dependency import compute_dependency
  9from slither.core.compilation_unit import SlitherCompilationUnit
 10from slither.core.declarations import Contract, Function
 11from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
 12from slither.core.declarations.enum_top_level import EnumTopLevel
 13from slither.core.declarations.event_top_level import EventTopLevel
 14from slither.core.declarations.function_top_level import FunctionTopLevel
 15from slither.core.declarations.import_directive import Import
 16from slither.core.declarations.pragma_directive import Pragma
 17from slither.core.declarations.structure_top_level import StructureTopLevel
 18from slither.core.declarations.using_for_top_level import UsingForTopLevel
 19from slither.core.scope.scope import FileScope
 20from slither.core.solidity_types import ElementaryType, TypeAliasTopLevel
 21from slither.core.variables.top_level_variable import TopLevelVariable
 22from slither.exceptions import SlitherException
 23from slither.solc_parsing.declarations.caller_context import CallerContextExpression
 24from slither.solc_parsing.declarations.contract import ContractSolc
 25from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
 26from slither.solc_parsing.declarations.function import FunctionSolc
 27from slither.solc_parsing.declarations.event_top_level import EventTopLevelSolc
 28from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc
 29from slither.solc_parsing.declarations.using_for_top_level import UsingForTopLevelSolc
 30from slither.solc_parsing.exceptions import VariableNotFound
 31from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc
 32
 33logging.basicConfig()
 34logger = logging.getLogger("SlitherSolcParsing")
 35logger.setLevel(logging.INFO)
 36
 37
 38class InheritanceResolutionError(SlitherException):
 39    pass
 40
 41
 42def _handle_import_aliases(
 43    symbol_aliases: dict, import_directive: Import, scope: FileScope
 44) -> None:
 45    """
 46    Handle the parsing of import aliases
 47
 48    Args:
 49        symbol_aliases (Dict): json dict from solc
 50        import_directive (Import): current import directive
 51        scope (FileScope): current file scape
 52
 53    Returns:
 54
 55    """
 56    for symbol_alias in symbol_aliases:
 57        if "foreign" in symbol_alias and "local" in symbol_alias:
 58            if isinstance(symbol_alias["foreign"], dict) and "name" in symbol_alias["foreign"]:
 59                original_name = symbol_alias["foreign"]["name"]
 60                local_name = symbol_alias["local"]
 61                import_directive.renaming[local_name] = original_name
 62                # Assuming that two imports cannot collide in renaming
 63                scope.renaming[local_name] = original_name
 64
 65            # This path should only be hit for the malformed AST of solc 0.5.12 where
 66            # the foreign identifier cannot be found but is required to resolve the alias.
 67            # see https://github.com/crytic/slither/issues/1319
 68            elif symbol_alias["local"]:
 69                raise SlitherException(
 70                    "Cannot resolve local alias for import directive due to malformed AST. Please upgrade to solc 0.6.0 or higher."
 71                )
 72
 73
 74class SlitherCompilationUnitSolc(CallerContextExpression):
 75    def __init__(self, compilation_unit: SlitherCompilationUnit) -> None:
 76        super().__init__()
 77
 78        self._compilation_unit: SlitherCompilationUnit = compilation_unit
 79
 80        self._contracts_by_id: dict[int, Contract] = {}
 81        # For top level functions, there should only be one `Function` since they can't be virtual and therefore can't be overridden.
 82        self._functions_by_id: dict[int, list[Function]] = defaultdict(list)
 83        self.imports_by_id: dict[int, Import] = {}
 84        self.top_level_events_by_id: dict[int, EventTopLevel] = {}
 85        self.top_level_errors_by_id: dict[int, EventTopLevel] = {}
 86        self.top_level_structures_by_id: dict[int, StructureTopLevel] = {}
 87        self.top_level_variables_by_id: dict[int, TopLevelVariable] = {}
 88        self.top_level_type_aliases_by_id: dict[int, TypeAliasTopLevel] = {}
 89        self.top_level_enums_by_id: dict[int, EnumTopLevel] = {}
 90
 91        self._parsed = False
 92        self._analyzed = False
 93        self._is_compact_ast = False
 94
 95        self._underlying_contract_to_parser: dict[Contract, ContractSolc] = {}
 96        self._structures_top_level_parser: list[StructureTopLevelSolc] = []
 97        self._custom_error_parser: list[CustomErrorSolc] = []
 98        self._variables_top_level_parser: list[TopLevelVariableSolc] = []
 99        self._functions_top_level_parser: list[FunctionSolc] = []
100        self._using_for_top_level_parser: list[UsingForTopLevelSolc] = []
101        self._events_top_level_parser: list[EventTopLevelSolc] = []
102        self._all_functions_and_modifier_parser: list[FunctionSolc] = []
103
104        self._top_level_contracts_counter = 0
105
106    @property
107    def compilation_unit(self) -> SlitherCompilationUnit:
108        return self._compilation_unit
109
110    @property
111    def all_functions_and_modifiers_parser(self) -> list[FunctionSolc]:
112        return self._all_functions_and_modifier_parser
113
114    def add_function_or_modifier_parser(self, f: FunctionSolc) -> None:
115        self._all_functions_and_modifier_parser.append(f)
116        self._functions_by_id[f.underlying_function.id].append(f.underlying_function)
117
118    @property
119    def underlying_contract_to_parser(self) -> dict[Contract, ContractSolc]:
120        return self._underlying_contract_to_parser
121
122    @property
123    def slither_parser(self) -> "SlitherCompilationUnitSolc":
124        return self
125
126    @property
127    def contracts_by_id(self) -> dict[int, Contract]:
128        return self._contracts_by_id
129
130    @property
131    def functions_by_id(self) -> dict[int, list[Function]]:
132        return self._functions_by_id
133
134    ###################################################################################
135    ###################################################################################
136    # region AST
137    ###################################################################################
138    ###################################################################################
139
140    def get_key(self) -> str:
141        if self._is_compact_ast:
142            return "nodeType"
143        return "name"
144
145    def get_children(self) -> str:
146        if self._is_compact_ast:
147            return "nodes"
148        return "children"
149
150    @property
151    def is_compact_ast(self) -> bool:
152        return self._is_compact_ast
153
154    # endregion
155    ###################################################################################
156    ###################################################################################
157    # region Parsing
158    ###################################################################################
159    ###################################################################################
160
161    def parse_top_level_from_json(self, json_data: str) -> bool:
162        try:
163            data_loaded = json.loads(json_data)
164            # Truffle AST
165            if "ast" in data_loaded:
166                self.parse_top_level_items(data_loaded["ast"], data_loaded["sourcePath"])
167                return True
168            # solc AST, where the non-json text was removed
169            if "attributes" in data_loaded:
170                filename = data_loaded["attributes"]["absolutePath"]
171            else:
172                filename = data_loaded["absolutePath"]
173            self.parse_top_level_items(data_loaded, filename)
174            return True
175        except ValueError:
176            first = json_data.find("{")
177            if first != -1:
178                last = json_data.rfind("}") + 1
179                filename = json_data[0:first]
180                json_data = json_data[first:last]
181
182                data_loaded = json.loads(json_data)
183                self.parse_top_level_items(data_loaded, filename)
184                return True
185            return False
186
187    def _parse_enum(self, top_level_data: dict, filename: str) -> None:
188        if self.is_compact_ast:
189            name = top_level_data["name"]
190            canonicalName = top_level_data["canonicalName"]
191        else:
192            name = top_level_data["attributes"][self.get_key()]
193            if "canonicalName" in top_level_data["attributes"]:
194                canonicalName = top_level_data["attributes"]["canonicalName"]
195            else:
196                canonicalName = name
197        values = []
198        children = (
199            top_level_data["members"]
200            if "members" in top_level_data
201            else top_level_data.get("children", [])
202        )
203        for child in children:
204            assert child[self.get_key()] == "EnumValue"
205            if self.is_compact_ast:
206                values.append(child["name"])
207            else:
208                values.append(child["attributes"][self.get_key()])
209
210        scope = self.compilation_unit.get_scope(filename)
211        enum = EnumTopLevel(name, canonicalName, values, scope)
212        enum.set_offset(top_level_data["src"], self._compilation_unit)
213        self._compilation_unit.enums_top_level.append(enum)
214        scope.enums[name] = enum
215        refId = top_level_data["id"]
216        self.top_level_enums_by_id[refId] = enum
217
218    def parse_top_level_items(self, data_loaded: dict, filename: str) -> None:
219        if not data_loaded or data_loaded is None:
220            logger.error(
221                "crytic-compile returned an empty AST. "
222                "If you are trying to analyze a contract from etherscan or similar make sure it has source code available."
223            )
224            return
225
226        exported_symbols = {}
227        if "nodeType" in data_loaded:
228            self._is_compact_ast = True
229            exported_symbols = data_loaded.get("exportedSymbols", {})
230        else:
231            attributes = data_loaded.get("attributes", {})
232            exported_symbols = attributes.get("exportedSymbols", {})
233
234        if "sourcePaths" in data_loaded:
235            for sourcePath in data_loaded["sourcePaths"]:
236                if os.path.isfile(sourcePath):
237                    self._compilation_unit.core.add_source_code(sourcePath)
238
239        if data_loaded[self.get_key()] == "root":
240            logger.error("solc <0.4 is not supported")
241            return
242        if data_loaded[self.get_key()] == "SourceUnit":
243            self._parse_source_unit(data_loaded, filename)
244        else:
245            logger.error("solc version is not supported")
246            return
247
248        if self.get_children() not in data_loaded:
249            return
250
251        scope = self.compilation_unit.get_scope(filename)
252        # Exported symbols includes a reference ID to all top-level definitions the file exports,
253        # including def's brought in by imports (even transitively) and def's local to the file.
254        for refId in exported_symbols.values():
255            scope.exported_symbols |= set(refId)
256
257        for top_level_data in data_loaded[self.get_children()]:
258            if top_level_data[self.get_key()] == "ContractDefinition":
259                contract = Contract(self._compilation_unit, scope)
260                contract_parser = ContractSolc(self, contract, top_level_data)
261                scope.contracts[contract.name] = contract
262                if "src" in top_level_data:
263                    contract.set_offset(top_level_data["src"], self._compilation_unit)
264
265                self._underlying_contract_to_parser[contract] = contract_parser
266
267            elif top_level_data[self.get_key()] == "PragmaDirective":
268                if self._is_compact_ast:
269                    pragma = Pragma(top_level_data["literals"], scope)
270                    scope.pragmas.add(pragma)
271                else:
272                    pragma = Pragma(top_level_data["attributes"]["literals"], scope)
273                    scope.pragmas.add(pragma)
274                pragma.set_offset(top_level_data["src"], self._compilation_unit)
275                self._compilation_unit.pragma_directives.append(pragma)
276
277            elif top_level_data[self.get_key()] == "UsingForDirective":
278                scope = self.compilation_unit.get_scope(filename)
279                usingFor = UsingForTopLevel(scope)
280                usingFor_parser = UsingForTopLevelSolc(usingFor, top_level_data, self)
281                usingFor.set_offset(top_level_data["src"], self._compilation_unit)
282                scope.using_for_directives.add(usingFor)
283
284                self._compilation_unit.using_for_top_level.append(usingFor)
285                self._using_for_top_level_parser.append(usingFor_parser)
286
287            elif top_level_data[self.get_key()] == "ImportDirective":
288                referenceId = top_level_data["id"]
289                if self.is_compact_ast:
290                    import_directive = Import(
291                        Path(
292                            top_level_data["absolutePath"],
293                        ),
294                        scope,
295                    )
296                    scope.imports.add(import_directive)
297                    # TODO investigate unitAlias in version < 0.7 and legacy ast
298                    if "unitAlias" in top_level_data:
299                        import_directive.alias = top_level_data["unitAlias"]
300                    if "symbolAliases" in top_level_data:
301                        symbol_aliases = top_level_data["symbolAliases"]
302                        _handle_import_aliases(symbol_aliases, import_directive, scope)
303                else:
304                    import_directive = Import(
305                        Path(
306                            top_level_data["attributes"].get("absolutePath", ""),
307                        ),
308                        scope,
309                    )
310                    scope.imports.add(import_directive)
311                    # TODO investigate unitAlias in version < 0.7 and legacy ast
312                    if (
313                        "attributes" in top_level_data
314                        and "unitAlias" in top_level_data["attributes"]
315                    ):
316                        import_directive.alias = top_level_data["attributes"]["unitAlias"]
317                import_directive.set_offset(top_level_data["src"], self._compilation_unit)
318                self._compilation_unit.import_directives.append(import_directive)
319                self.imports_by_id[referenceId] = import_directive
320
321                get_imported_scope = self.compilation_unit.get_scope(import_directive.filename)
322                scope.accessible_scopes.append(get_imported_scope)
323
324            elif top_level_data[self.get_key()] == "StructDefinition":
325                st = StructureTopLevel(self.compilation_unit, scope)
326                st.set_offset(top_level_data["src"], self._compilation_unit)
327                st_parser = StructureTopLevelSolc(st, top_level_data, self)
328                scope.structures[st.name] = st
329
330                self._compilation_unit.structures_top_level.append(st)
331                self._structures_top_level_parser.append(st_parser)
332                referenceId = top_level_data["id"]
333                self.top_level_structures_by_id[referenceId] = st
334
335            elif top_level_data[self.get_key()] == "EnumDefinition":
336                # Note enum don't need a complex parser, so everything is directly done
337                self._parse_enum(top_level_data, filename)
338
339            elif top_level_data[self.get_key()] == "VariableDeclaration":
340                var = TopLevelVariable(scope)
341                var_parser = TopLevelVariableSolc(var, top_level_data, self)
342                var.set_offset(top_level_data["src"], self._compilation_unit)
343
344                self._compilation_unit.variables_top_level.append(var)
345                self._variables_top_level_parser.append(var_parser)
346                scope.variables[var.name] = var
347                referenceId = top_level_data["id"]
348                self.top_level_variables_by_id[referenceId] = var
349
350            elif top_level_data[self.get_key()] == "FunctionDefinition":
351                func = FunctionTopLevel(self._compilation_unit, scope)
352                scope.functions.add(func)
353                func.set_offset(top_level_data["src"], self._compilation_unit)
354                func_parser = FunctionSolc(func, top_level_data, None, self)
355
356                self._compilation_unit.functions_top_level.append(func)
357                self._functions_top_level_parser.append(func_parser)
358                self.add_function_or_modifier_parser(func_parser)
359
360            elif top_level_data[self.get_key()] == "ErrorDefinition":
361                custom_error = CustomErrorTopLevel(self._compilation_unit, scope)
362                custom_error.set_offset(top_level_data["src"], self._compilation_unit)
363
364                custom_error_parser = CustomErrorSolc(custom_error, top_level_data, None, self)
365                scope.custom_errors.add(custom_error)
366                self._compilation_unit.custom_errors.append(custom_error)
367                self._custom_error_parser.append(custom_error_parser)
368                referenceId = top_level_data["id"]
369                self.top_level_errors_by_id[referenceId] = custom_error
370
371            elif top_level_data[self.get_key()] == "UserDefinedValueTypeDefinition":
372                assert "name" in top_level_data
373                alias = top_level_data["name"]
374                assert "underlyingType" in top_level_data
375                underlying_type = top_level_data["underlyingType"]
376                assert (
377                    "nodeType" in underlying_type
378                    and underlying_type["nodeType"] == "ElementaryTypeName"
379                )
380                assert "name" in underlying_type
381
382                original_type = ElementaryType(underlying_type["name"])
383
384                type_alias = TypeAliasTopLevel(original_type, alias, scope)
385                type_alias.set_offset(top_level_data["src"], self._compilation_unit)
386                self._compilation_unit.type_aliases[alias] = type_alias
387                scope.type_aliases[alias] = type_alias
388                referenceId = top_level_data["id"]
389                self.top_level_type_aliases_by_id[referenceId] = type_alias
390
391            elif top_level_data[self.get_key()] == "EventDefinition":
392                event = EventTopLevel(scope)
393                event.set_offset(top_level_data["src"], self._compilation_unit)
394
395                event_parser = EventTopLevelSolc(event, top_level_data, self)  # type: ignore
396                self._events_top_level_parser.append(event_parser)
397                scope.events.add(event)
398                self._compilation_unit.events_top_level.append(event)
399                referenceId = top_level_data["id"]
400                self.top_level_events_by_id[referenceId] = event
401
402            else:
403                raise SlitherException(f"Top level {top_level_data[self.get_key()]} not supported")
404
405    def _parse_source_unit(self, data: dict, filename: str) -> None:
406        if data[self.get_key()] != "SourceUnit":
407            return  # handle solc prior 0.3.6
408
409        # match any char for filename
410        # filename can contain space, /, -, ..
411        name_candidates = re.findall("=+ (.+) =+", filename)
412        if name_candidates:
413            assert len(name_candidates) == 1
414            name: str = name_candidates[0]
415        else:
416            name = filename
417
418        sourceUnit = -1  # handle old solc, or error
419        if "src" in data:
420            sourceUnit_candidates = re.findall("[0-9]*:[0-9]*:([0-9]*)", data["src"])
421            if len(sourceUnit_candidates) == 1:
422                sourceUnit = int(sourceUnit_candidates[0])
423        if sourceUnit == -1:
424            # if source unit is not found
425            # We can still deduce it, by assigning to the last source_code added
426            # This works only for crytic compile.
427            # which used --combined-json ast, rather than --ast-json
428            # As a result -1 is not used as index
429            if self._compilation_unit.core.crytic_compile is not None:
430                sourceUnit = len(self._compilation_unit.core.source_code)
431
432        self._compilation_unit.source_units[sourceUnit] = name
433        if os.path.isfile(name) and name not in self._compilation_unit.core.source_code:
434            self._compilation_unit.core.add_source_code(name)
435        else:
436            lib_name = os.path.join("node_modules", name)
437            if os.path.isfile(lib_name) and name not in self._compilation_unit.core.source_code:
438                self._compilation_unit.core.add_source_code(lib_name)
439
440    # endregion
441    ###################################################################################
442    ###################################################################################
443    # region Analyze
444    ###################################################################################
445    ###################################################################################
446
447    @property
448    def parsed(self) -> bool:
449        return self._parsed
450
451    @property
452    def analyzed(self) -> bool:
453        return self._analyzed
454
455    def parse_contracts(self) -> None:
456        if not self._underlying_contract_to_parser:
457            logger.info(
458                f"No contracts were found in {self._compilation_unit.core.filename}, check the correct compilation"
459            )
460        if self._parsed:
461            raise Exception("Contract analysis can be run only once!")
462
463        def resolve_remapping_and_renaming(contract_parser: ContractSolc, want: str) -> Contract:
464            contract_name = contract_parser.remapping[want]
465            target = None
466            # For contracts that are imported and aliased e.g. 'import {A as B} from "./C.sol"',
467            # we look through the imports's (`Import`) renaming to find the original contract name
468            # and then look up the original contract in the import path's scope (`FileScope`).
469            for import_ in contract_parser.underlying_contract.file_scope.imports:
470                if contract_name in import_.renaming:
471                    target = self.compilation_unit.get_scope(
472                        import_.filename
473                    ).get_contract_from_name(import_.renaming[contract_name])
474
475            # Fallback to the current file scope if the contract is not found in the import path's scope.
476            # It is assumed that it isn't possible to defined a contract with the same name as "aliased" names.
477            if target is None:
478                target = contract_parser.underlying_contract.file_scope.get_contract_from_name(
479                    contract_name
480                )
481
482            if target == contract_parser.underlying_contract:
483                raise InheritanceResolutionError(
484                    "Could not resolve contract inheritance. This is likely caused by an import renaming that collides with existing names (see https://github.com/crytic/slither/issues/1758)."
485                    f"\n Try changing `contract {target}` ({target.source_mapping}) to a unique name as a workaround."
486                    "\n Please share the source code that caused this error here: https://github.com/crytic/slither/issues/"
487                )
488            assert target, f"Contract {contract_name} not found"
489            return target
490
491        # Update of the inheritance
492        for contract_parser in self._underlying_contract_to_parser.values():
493            ancestors = []
494            fathers = []
495            father_constructors = []
496            missing_inheritance = None
497
498            # Resolve linearized base contracts.
499            # Remove the first elem in linearizedBaseContracts as it is the contract itself.
500            for i in contract_parser.linearized_base_contracts[1:]:
501                if i in contract_parser.remapping:
502                    target = resolve_remapping_and_renaming(contract_parser, i)
503                    ancestors.append(target)
504                elif i in self._contracts_by_id:
505                    ancestors.append(self._contracts_by_id[i])
506                else:
507                    missing_inheritance = i
508
509            # Resolve immediate base contracts and attach references.
510            for i, src in contract_parser.baseContracts:
511                if i in contract_parser.remapping:
512                    target = resolve_remapping_and_renaming(contract_parser, i)
513                    fathers.append(target)
514                    target.add_reference_from_raw_source(src, self.compilation_unit)
515                elif i in self._contracts_by_id:
516                    target = self._contracts_by_id[i]
517                    fathers.append(target)
518                    target.add_reference_from_raw_source(src, self.compilation_unit)
519                else:
520                    missing_inheritance = i
521
522            # Resolve immediate base constructor calls.
523            for i in contract_parser.baseConstructorContractsCalled:
524                if i in contract_parser.remapping:
525                    target = resolve_remapping_and_renaming(contract_parser, i)
526                    father_constructors.append(target)
527                elif i in self._contracts_by_id:
528                    father_constructors.append(self._contracts_by_id[i])
529                else:
530                    missing_inheritance = i
531
532            contract_parser.underlying_contract.set_inheritance(
533                ancestors, fathers, father_constructors
534            )
535
536            if missing_inheritance:
537                self._compilation_unit.contracts_with_missing_inheritance.add(
538                    contract_parser.underlying_contract
539                )
540                txt = f"Missing inheritance {contract_parser.underlying_contract} ({contract_parser.compilation_unit.crytic_compile_compilation_unit.unique_id})\n"
541                txt += f"Missing inheritance ID: {missing_inheritance}\n"
542                if contract_parser.underlying_contract.inheritance:
543                    txt += "Inheritance found:\n"
544                    for contract_inherited in contract_parser.underlying_contract.inheritance:
545                        txt += f"\t - {contract_inherited} (ID {contract_inherited.id})\n"
546                contract_parser.log_incorrect_parsing(txt)
547
548                contract_parser.set_is_analyzed(True)
549                contract_parser.delete_content()
550
551        contracts_to_be_analyzed = list(self._underlying_contract_to_parser.values())
552
553        # Any contract can refer another contract enum without need for inheritance
554        self._analyze_all_enums(contracts_to_be_analyzed)
555
556        [c.set_is_analyzed(False) for c in self._underlying_contract_to_parser.values()]
557
558        libraries = [
559            c for c in contracts_to_be_analyzed if c.underlying_contract.contract_kind == "library"
560        ]
561        contracts_to_be_analyzed = [
562            c for c in contracts_to_be_analyzed if c.underlying_contract.contract_kind != "library"
563        ]
564
565        # We first parse the struct/variables/functions/contract
566        self._analyze_first_part(contracts_to_be_analyzed, libraries)
567
568        [c.set_is_analyzed(False) for c in self._underlying_contract_to_parser.values()]
569
570        # We analyze the struct and parse and analyze the events
571        # A contract can refer in the variables a struct or a event from any contract
572        # (without inheritance link)
573        self._analyze_second_part(contracts_to_be_analyzed, libraries)
574        [c.set_is_analyzed(False) for c in self._underlying_contract_to_parser.values()]
575
576        # Then we analyse state variables, functions and modifiers
577        self._analyze_third_part(contracts_to_be_analyzed, libraries)
578        [c.set_is_analyzed(False) for c in self._underlying_contract_to_parser.values()]
579
580        self._analyze_using_for(contracts_to_be_analyzed, libraries)
581
582        self._parsed = True
583
584    def analyze_contracts(self) -> None:
585        from slither.utils.timing import PhaseTimer
586
587        if not self._parsed:
588            raise SlitherException("Parse the contract before running analyses")
589
590        timer = PhaseTimer.get()
591
592        with timer.phase("convert_to_slithir"):
593            self._convert_to_slithir()
594
595        if not self._compilation_unit.core.skip_data_dependency:
596            with timer.phase("compute_dependency"):
597                compute_dependency(self._compilation_unit)
598
599        with timer.phase("compute_storage_layout"):
600            self._compilation_unit.compute_storage_layout()
601
602        self._analyzed = True
603
604    def _analyze_all_enums(self, contracts_to_be_analyzed: list[ContractSolc]) -> None:
605        while contracts_to_be_analyzed:
606            contract = contracts_to_be_analyzed[0]
607
608            contracts_to_be_analyzed = contracts_to_be_analyzed[1:]
609            all_father_analyzed = all(
610                self._underlying_contract_to_parser[father].is_analyzed
611                for father in contract.underlying_contract.inheritance
612            )
613
614            if not contract.underlying_contract.inheritance or all_father_analyzed:
615                self._analyze_enums(contract)
616            else:
617                contracts_to_be_analyzed += [contract]
618
619    def _analyze_first_part(
620        self,
621        contracts_to_be_analyzed: list[ContractSolc],
622        libraries: list[ContractSolc],
623    ) -> None:
624        for lib in libraries:
625            self._parse_struct_var_modifiers_functions(lib)
626
627        # Start with the contracts without inheritance
628        # Analyze a contract only if all its fathers
629        # Were analyzed
630        while contracts_to_be_analyzed:
631            contract = contracts_to_be_analyzed[0]
632
633            contracts_to_be_analyzed = contracts_to_be_analyzed[1:]
634            all_father_analyzed = all(
635                self._underlying_contract_to_parser[father].is_analyzed
636                for father in contract.underlying_contract.inheritance
637            )
638
639            if not contract.underlying_contract.inheritance or all_father_analyzed:
640                self._parse_struct_var_modifiers_functions(contract)
641
642            else:
643                contracts_to_be_analyzed += [contract]
644
645    def _analyze_second_part(
646        self,
647        contracts_to_be_analyzed: list[ContractSolc],
648        libraries: list[ContractSolc],
649    ) -> None:
650        for lib in libraries:
651            self._analyze_struct_events(lib)
652
653        self._analyze_top_level_variables()
654        self._analyze_top_level_structures()
655        self._analyze_top_level_events()
656
657        # Start with the contracts without inheritance
658        # Analyze a contract only if all its fathers
659        # Were analyzed
660        while contracts_to_be_analyzed:
661            contract = contracts_to_be_analyzed[0]
662
663            contracts_to_be_analyzed = contracts_to_be_analyzed[1:]
664            all_father_analyzed = all(
665                self._underlying_contract_to_parser[father].is_analyzed
666                for father in contract.underlying_contract.inheritance
667            )
668
669            if not contract.underlying_contract.inheritance or all_father_analyzed:
670                self._analyze_struct_events(contract)
671
672            else:
673                contracts_to_be_analyzed += [contract]
674
675    def _analyze_third_part(
676        self,
677        contracts_to_be_analyzed: list[ContractSolc],
678        libraries: list[ContractSolc],
679    ) -> None:
680        for lib in libraries:
681            self._analyze_variables_modifiers_functions(lib)
682
683        # Start with the contracts without inheritance
684        # Analyze a contract only if all its fathers
685        # Were analyzed
686        while contracts_to_be_analyzed:
687            contract = contracts_to_be_analyzed[0]
688
689            contracts_to_be_analyzed = contracts_to_be_analyzed[1:]
690            all_father_analyzed = all(
691                self._underlying_contract_to_parser[father].is_analyzed
692                for father in contract.underlying_contract.inheritance
693            )
694
695            if not contract.underlying_contract.inheritance or all_father_analyzed:
696                self._analyze_variables_modifiers_functions(contract)
697
698            else:
699                contracts_to_be_analyzed += [contract]
700
701    def _analyze_using_for(
702        self, contracts_to_be_analyzed: list[ContractSolc], libraries: list[ContractSolc]
703    ) -> None:
704        self._analyze_top_level_using_for()
705
706        for lib in libraries:
707            lib.analyze_using_for()
708
709        while contracts_to_be_analyzed:
710            contract = contracts_to_be_analyzed[0]
711
712            contracts_to_be_analyzed = contracts_to_be_analyzed[1:]
713            all_father_analyzed = all(
714                self._underlying_contract_to_parser[father].is_analyzed
715                for father in contract.underlying_contract.inheritance
716            )
717
718            if not contract.underlying_contract.inheritance or all_father_analyzed:
719                contract.analyze_using_for()
720                contract.set_is_analyzed(True)
721            else:
722                contracts_to_be_analyzed += [contract]
723
724    def _analyze_enums(self, contract: ContractSolc) -> None:
725        # Enum must be analyzed first
726        contract.analyze_enums()
727        contract.set_is_analyzed(True)
728
729    def _parse_struct_var_modifiers_functions(self, contract: ContractSolc) -> None:
730        contract.parse_structs()  # struct can refer another struct
731        contract.parse_state_variables()
732        contract.parse_modifiers()
733        contract.parse_functions()
734        contract.parse_custom_errors()
735        contract.parse_type_alias()
736        contract.set_is_analyzed(True)
737
738    def _analyze_struct_events(self, contract: ContractSolc) -> None:
739        contract.analyze_constant_state_variables()
740
741        # Struct can refer to enum, or state variables
742        contract.analyze_structs()
743        # Event can refer to struct
744        contract.analyze_events()
745
746        contract.analyze_custom_errors()
747
748        # Must be here since it can use top level variables hence they must be analyzed
749        contract.analyze_storage_layout()
750
751        contract.set_is_analyzed(True)
752
753    def _analyze_top_level_structures(self) -> None:
754        try:
755            for struct in self._structures_top_level_parser:
756                struct.analyze()
757        except (VariableNotFound, KeyError) as e:
758            raise SlitherException(f"Missing struct {e} during top level structure analyze") from e
759
760    def _analyze_top_level_variables(self) -> None:
761        try:
762            for var in self._variables_top_level_parser:
763                var.analyze(var)
764        except (VariableNotFound, KeyError) as e:
765            raise SlitherException(f"Missing {e} during variable analyze") from e
766
767    def _analyze_top_level_events(self) -> None:
768        try:
769            for event in self._events_top_level_parser:
770                event.analyze()
771        except (VariableNotFound, KeyError) as e:
772            raise SlitherException(f"Missing event {e} during top level event analyze") from e
773
774    def _analyze_params_top_level_function(self) -> None:
775        for func_parser in self._functions_top_level_parser:
776            func_parser.analyze_params()
777            self._compilation_unit.add_function(func_parser.underlying_function)
778
779    def _analyze_top_level_using_for(self) -> None:
780        for using_for in self._using_for_top_level_parser:
781            using_for.analyze()
782
783    def _analyze_params_custom_error(self) -> None:
784        for custom_error_parser in self._custom_error_parser:
785            custom_error_parser.analyze_params()
786
787    def _analyze_content_top_level_function(self) -> None:
788        try:
789            for func_parser in self._functions_top_level_parser:
790                func_parser.analyze_content()
791        except (VariableNotFound, KeyError) as e:
792            raise SlitherException(f"Missing {e} during top level function analyze") from e
793
794    def _analyze_variables_modifiers_functions(self, contract: ContractSolc) -> None:
795        # State variables, modifiers and functions can refer to anything
796
797        contract.analyze_params_modifiers()
798        contract.analyze_params_functions()
799        self._analyze_params_top_level_function()
800        self._analyze_params_custom_error()
801
802        contract.analyze_state_variables()
803
804        contract.analyze_content_modifiers()
805        contract.analyze_content_functions()
806        self._analyze_content_top_level_function()
807
808        contract.set_is_analyzed(True)
809
810    def _convert_to_slithir(self) -> None:
811        for contract in self._compilation_unit.contracts:
812            contract.add_constructor_variables()
813
814            for func in contract.functions + contract.modifiers:
815                try:
816                    func.generate_slithir_and_analyze()
817
818                except AttributeError as e:
819                    # This can happens for example if there is a call to an interface
820                    # And the interface is redefined due to contract's name reuse
821                    # But the available version misses some functions
822                    self._underlying_contract_to_parser[contract].log_incorrect_parsing(
823                        f"Impossible to generate IR for {contract.name}.{func.name} ({func.source_mapping}):\n {e}"
824                    )
825                except Exception as e:
826                    func_expressions = "\n".join([f"\t{ex}" for ex in func.expressions])
827                    logger.error(
828                        f"\nFailed to generate IR for {contract.name}.{func.name}. Please open an issue https://github.com/crytic/slither/issues.\n{contract.name}.{func.name} ({func.source_mapping}):\n "
829                        f"{func_expressions}"
830                    )
831                    raise e
832            try:
833                contract.convert_expression_to_slithir_ssa()
834            except Exception as e:
835                logger.error(
836                    f"\nFailed to convert IR to SSA for {contract.name} contract. Please open an issue https://github.com/crytic/slither/issues.\n "
837                )
838                raise e
839
840        for func in self._compilation_unit.functions_top_level:
841            try:
842                func.generate_slithir_and_analyze()
843            except AttributeError as e:
844                logger.error(
845                    f"Impossible to generate IR for top level function {func.name} ({func.source_mapping}):\n {e}"
846                )
847            except Exception as e:
848                func_expressions = "\n".join([f"\t{ex}" for ex in func.expressions])
849                logger.error(
850                    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 "
851                    f"{func_expressions}"
852                )
853                raise e
854
855            try:
856                func.generate_slithir_ssa({})
857            except Exception as e:
858                func_expressions = "\n".join([f"\t{ex}" for ex in func.expressions])
859                logger.error(
860                    f"\nFailed to convert IR to SSA for top level function {func.name}. Please open an issue https://github.com/crytic/slither/issues.\n{func.name} ({func.source_mapping}):\n "
861                    f"{func_expressions}"
862                )
863                raise e
864
865        self._compilation_unit.propagate_function_calls()
866        for contract in self._compilation_unit.contracts:
867            contract.fix_phi()
868            contract.update_read_write_using_ssa()
869
870    # endregion
logger = <Logger SlitherSolcParsing (INFO)>
class InheritanceResolutionError(slither.exceptions.SlitherException):
39class InheritanceResolutionError(SlitherException):
40    pass

Common base class for all non-exit exceptions.

class SlitherCompilationUnitSolc(slither.solc_parsing.declarations.caller_context.CallerContextExpression):
 75class SlitherCompilationUnitSolc(CallerContextExpression):
 76    def __init__(self, compilation_unit: SlitherCompilationUnit) -> None:
 77        super().__init__()
 78
 79        self._compilation_unit: SlitherCompilationUnit = compilation_unit
 80
 81        self._contracts_by_id: dict[int, Contract] = {}
 82        # For top level functions, there should only be one `Function` since they can't be virtual and therefore can't be overridden.
 83        self._functions_by_id: dict[int, list[Function]] = defaultdict(list)
 84        self.imports_by_id: dict[int, Import] = {}
 85        self.top_level_events_by_id: dict[int, EventTopLevel] = {}
 86        self.top_level_errors_by_id: dict[int, EventTopLevel] = {}
 87        self.top_level_structures_by_id: dict[int, StructureTopLevel] = {}
 88        self.top_level_variables_by_id: dict[int, TopLevelVariable] = {}
 89        self.top_level_type_aliases_by_id: dict[int, TypeAliasTopLevel] = {}
 90        self.top_level_enums_by_id: dict[int, EnumTopLevel] = {}
 91
 92        self._parsed = False
 93        self._analyzed = False
 94        self._is_compact_ast = False
 95
 96        self._underlying_contract_to_parser: dict[Contract, ContractSolc] = {}
 97        self._structures_top_level_parser: list[StructureTopLevelSolc] = []
 98        self._custom_error_parser: list[CustomErrorSolc] = []
 99        self._variables_top_level_parser: list[TopLevelVariableSolc] = []
100        self._functions_top_level_parser: list[FunctionSolc] = []
101        self._using_for_top_level_parser: list[UsingForTopLevelSolc] = []
102        self._events_top_level_parser: list[EventTopLevelSolc] = []
103        self._all_functions_and_modifier_parser: list[FunctionSolc] = []
104
105        self._top_level_contracts_counter = 0
106
107    @property
108    def compilation_unit(self) -> SlitherCompilationUnit:
109        return self._compilation_unit
110
111    @property
112    def all_functions_and_modifiers_parser(self) -> list[FunctionSolc]:
113        return self._all_functions_and_modifier_parser
114
115    def add_function_or_modifier_parser(self, f: FunctionSolc) -> None:
116        self._all_functions_and_modifier_parser.append(f)
117        self._functions_by_id[f.underlying_function.id].append(f.underlying_function)
118
119    @property
120    def underlying_contract_to_parser(self) -> dict[Contract, ContractSolc]:
121        return self._underlying_contract_to_parser
122
123    @property
124    def slither_parser(self) -> "SlitherCompilationUnitSolc":
125        return self
126
127    @property
128    def contracts_by_id(self) -> dict[int, Contract]:
129        return self._contracts_by_id
130
131    @property
132    def functions_by_id(self) -> dict[int, list[Function]]:
133        return self._functions_by_id
134
135    ###################################################################################
136    ###################################################################################
137    # region AST
138    ###################################################################################
139    ###################################################################################
140
141    def get_key(self) -> str:
142        if self._is_compact_ast:
143            return "nodeType"
144        return "name"
145
146    def get_children(self) -> str:
147        if self._is_compact_ast:
148            return "nodes"
149        return "children"
150
151    @property
152    def is_compact_ast(self) -> bool:
153        return self._is_compact_ast
154
155    # endregion
156    ###################################################################################
157    ###################################################################################
158    # region Parsing
159    ###################################################################################
160    ###################################################################################
161
162    def parse_top_level_from_json(self, json_data: str) -> bool:
163        try:
164            data_loaded = json.loads(json_data)
165            # Truffle AST
166            if "ast" in data_loaded:
167                self.parse_top_level_items(data_loaded["ast"], data_loaded["sourcePath"])
168                return True
169            # solc AST, where the non-json text was removed
170            if "attributes" in data_loaded:
171                filename = data_loaded["attributes"]["absolutePath"]
172            else:
173                filename = data_loaded["absolutePath"]
174            self.parse_top_level_items(data_loaded, filename)
175            return True
176        except ValueError:
177            first = json_data.find("{")
178            if first != -1:
179                last = json_data.rfind("}") + 1
180                filename = json_data[0:first]
181                json_data = json_data[first:last]
182
183                data_loaded = json.loads(json_data)
184                self.parse_top_level_items(data_loaded, filename)
185                return True
186            return False
187
188    def _parse_enum(self, top_level_data: dict, filename: str) -> None:
189        if self.is_compact_ast:
190            name = top_level_data["name"]
191            canonicalName = top_level_data["canonicalName"]
192        else:
193            name = top_level_data["attributes"][self.get_key()]
194            if "canonicalName" in top_level_data["attributes"]:
195                canonicalName = top_level_data["attributes"]["canonicalName"]
196            else:
197                canonicalName = name
198        values = []
199        children = (
200            top_level_data["members"]
201            if "members" in top_level_data
202            else top_level_data.get("children", [])
203        )
204        for child in children:
205            assert child[self.get_key()] == "EnumValue"
206            if self.is_compact_ast:
207                values.append(child["name"])
208            else:
209                values.append(child["attributes"][self.get_key()])
210
211        scope = self.compilation_unit.get_scope(filename)
212        enum = EnumTopLevel(name, canonicalName, values, scope)
213        enum.set_offset(top_level_data["src"], self._compilation_unit)
214        self._compilation_unit.enums_top_level.append(enum)
215        scope.enums[name] = enum
216        refId = top_level_data["id"]
217        self.top_level_enums_by_id[refId] = enum
218
219    def parse_top_level_items(self, data_loaded: dict, filename: str) -> None:
220        if not data_loaded or data_loaded is None:
221            logger.error(
222                "crytic-compile returned an empty AST. "
223                "If you are trying to analyze a contract from etherscan or similar make sure it has source code available."
224            )
225            return
226
227        exported_symbols = {}
228        if "nodeType" in data_loaded:
229            self._is_compact_ast = True
230            exported_symbols = data_loaded.get("exportedSymbols", {})
231        else:
232            attributes = data_loaded.get("attributes", {})
233            exported_symbols = attributes.get("exportedSymbols", {})
234
235        if "sourcePaths" in data_loaded:
236            for sourcePath in data_loaded["sourcePaths"]:
237                if os.path.isfile(sourcePath):
238                    self._compilation_unit.core.add_source_code(sourcePath)
239
240        if data_loaded[self.get_key()] == "root":
241            logger.error("solc <0.4 is not supported")
242            return
243        if data_loaded[self.get_key()] == "SourceUnit":
244            self._parse_source_unit(data_loaded, filename)
245        else:
246            logger.error("solc version is not supported")
247            return
248
249        if self.get_children() not in data_loaded:
250            return
251
252        scope = self.compilation_unit.get_scope(filename)
253        # Exported symbols includes a reference ID to all top-level definitions the file exports,
254        # including def's brought in by imports (even transitively) and def's local to the file.
255        for refId in exported_symbols.values():
256            scope.exported_symbols |= set(refId)
257
258        for top_level_data in data_loaded[self.get_children()]:
259            if top_level_data[self.get_key()] == "ContractDefinition":
260                contract = Contract(self._compilation_unit, scope)
261                contract_parser = ContractSolc(self, contract, top_level_data)
262                scope.contracts[contract.name] = contract
263                if "src" in top_level_data:
264                    contract.set_offset(top_level_data["src"], self._compilation_unit)
265
266                self._underlying_contract_to_parser[contract] = contract_parser
267
268            elif top_level_data[self.get_key()] == "PragmaDirective":
269                if self._is_compact_ast:
270                    pragma = Pragma(top_level_data["literals"], scope)
271                    scope.pragmas.add(pragma)
272                else:
273                    pragma = Pragma(top_level_data["attributes"]["literals"], scope)
274                    scope.pragmas.add(pragma)
275                pragma.set_offset(top_level_data["src"], self._compilation_unit)
276                self._compilation_unit.pragma_directives.append(pragma)
277
278            elif top_level_data[self.get_key()] == "UsingForDirective":
279                scope = self.compilation_unit.get_scope(filename)
280                usingFor = UsingForTopLevel(scope)
281                usingFor_parser = UsingForTopLevelSolc(usingFor, top_level_data, self)
282                usingFor.set_offset(top_level_data["src"], self._compilation_unit)
283                scope.using_for_directives.add(usingFor)
284
285                self._compilation_unit.using_for_top_level.append(usingFor)
286                self._using_for_top_level_parser.append(usingFor_parser)
287
288            elif top_level_data[self.get_key()] == "ImportDirective":
289                referenceId = top_level_data["id"]
290                if self.is_compact_ast:
291                    import_directive = Import(
292                        Path(
293                            top_level_data["absolutePath"],
294                        ),
295                        scope,
296                    )
297                    scope.imports.add(import_directive)
298                    # TODO investigate unitAlias in version < 0.7 and legacy ast
299                    if "unitAlias" in top_level_data:
300                        import_directive.alias = top_level_data["unitAlias"]
301                    if "symbolAliases" in top_level_data:
302                        symbol_aliases = top_level_data["symbolAliases"]
303                        _handle_import_aliases(symbol_aliases, import_directive, scope)
304                else:
305                    import_directive = Import(
306                        Path(
307                            top_level_data["attributes"].get("absolutePath", ""),
308                        ),
309                        scope,
310                    )
311                    scope.imports.add(import_directive)
312                    # TODO investigate unitAlias in version < 0.7 and legacy ast
313                    if (
314                        "attributes" in top_level_data
315                        and "unitAlias" in top_level_data["attributes"]
316                    ):
317                        import_directive.alias = top_level_data["attributes"]["unitAlias"]
318                import_directive.set_offset(top_level_data["src"], self._compilation_unit)
319                self._compilation_unit.import_directives.append(import_directive)
320                self.imports_by_id[referenceId] = import_directive
321
322                get_imported_scope = self.compilation_unit.get_scope(import_directive.filename)
323                scope.accessible_scopes.append(get_imported_scope)
324
325            elif top_level_data[self.get_key()] == "StructDefinition":
326                st = StructureTopLevel(self.compilation_unit, scope)
327                st.set_offset(top_level_data["src"], self._compilation_unit)
328                st_parser = StructureTopLevelSolc(st, top_level_data, self)
329                scope.structures[st.name] = st
330
331                self._compilation_unit.structures_top_level.append(st)
332                self._structures_top_level_parser.append(st_parser)
333                referenceId = top_level_data["id"]
334                self.top_level_structures_by_id[referenceId] = st
335
336            elif top_level_data[self.get_key()] == "EnumDefinition":
337                # Note enum don't need a complex parser, so everything is directly done
338                self._parse_enum(top_level_data, filename)
339
340            elif top_level_data[self.get_key()] == "VariableDeclaration":
341                var = TopLevelVariable(scope)
342                var_parser = TopLevelVariableSolc(var, top_level_data, self)
343                var.set_offset(top_level_data["src"], self._compilation_unit)
344
345                self._compilation_unit.variables_top_level.append(var)
346                self._variables_top_level_parser.append(var_parser)
347                scope.variables[var.name] = var
348                referenceId = top_level_data["id"]
349                self.top_level_variables_by_id[referenceId] = var
350
351            elif top_level_data[self.get_key()] == "FunctionDefinition":
352                func = FunctionTopLevel(self._compilation_unit, scope)
353                scope.functions.add(func)
354                func.set_offset(top_level_data["src"], self._compilation_unit)
355                func_parser = FunctionSolc(func, top_level_data, None, self)
356
357                self._compilation_unit.functions_top_level.append(func)
358                self._functions_top_level_parser.append(func_parser)
359                self.add_function_or_modifier_parser(func_parser)
360
361            elif top_level_data[self.get_key()] == "ErrorDefinition":
362                custom_error = CustomErrorTopLevel(self._compilation_unit, scope)
363                custom_error.set_offset(top_level_data["src"], self._compilation_unit)
364
365                custom_error_parser = CustomErrorSolc(custom_error, top_level_data, None, self)
366                scope.custom_errors.add(custom_error)
367                self._compilation_unit.custom_errors.append(custom_error)
368                self._custom_error_parser.append(custom_error_parser)
369                referenceId = top_level_data["id"]
370                self.top_level_errors_by_id[referenceId] = custom_error
371
372            elif top_level_data[self.get_key()] == "UserDefinedValueTypeDefinition":
373                assert "name" in top_level_data
374                alias = top_level_data["name"]
375                assert "underlyingType" in top_level_data
376                underlying_type = top_level_data["underlyingType"]
377                assert (
378                    "nodeType" in underlying_type
379                    and underlying_type["nodeType"] == "ElementaryTypeName"
380                )
381                assert "name" in underlying_type
382
383                original_type = ElementaryType(underlying_type["name"])
384
385                type_alias = TypeAliasTopLevel(original_type, alias, scope)
386                type_alias.set_offset(top_level_data["src"], self._compilation_unit)
387                self._compilation_unit.type_aliases[alias] = type_alias
388                scope.type_aliases[alias] = type_alias
389                referenceId = top_level_data["id"]
390                self.top_level_type_aliases_by_id[referenceId] = type_alias
391
392            elif top_level_data[self.get_key()] == "EventDefinition":
393                event = EventTopLevel(scope)
394                event.set_offset(top_level_data["src"], self._compilation_unit)
395
396                event_parser = EventTopLevelSolc(event, top_level_data, self)  # type: ignore
397                self._events_top_level_parser.append(event_parser)
398                scope.events.add(event)
399                self._compilation_unit.events_top_level.append(event)
400                referenceId = top_level_data["id"]
401                self.top_level_events_by_id[referenceId] = event
402
403            else:
404                raise SlitherException(f"Top level {top_level_data[self.get_key()]} not supported")
405
406    def _parse_source_unit(self, data: dict, filename: str) -> None:
407        if data[self.get_key()] != "SourceUnit":
408            return  # handle solc prior 0.3.6
409
410        # match any char for filename
411        # filename can contain space, /, -, ..
412        name_candidates = re.findall("=+ (.+) =+", filename)
413        if name_candidates:
414            assert len(name_candidates) == 1
415            name: str = name_candidates[0]
416        else:
417            name = filename
418
419        sourceUnit = -1  # handle old solc, or error
420        if "src" in data:
421            sourceUnit_candidates = re.findall("[0-9]*:[0-9]*:([0-9]*)", data["src"])
422            if len(sourceUnit_candidates) == 1:
423                sourceUnit = int(sourceUnit_candidates[0])
424        if sourceUnit == -1:
425            # if source unit is not found
426            # We can still deduce it, by assigning to the last source_code added
427            # This works only for crytic compile.
428            # which used --combined-json ast, rather than --ast-json
429            # As a result -1 is not used as index
430            if self._compilation_unit.core.crytic_compile is not None:
431                sourceUnit = len(self._compilation_unit.core.source_code)
432
433        self._compilation_unit.source_units[sourceUnit] = name
434        if os.path.isfile(name) and name not in self._compilation_unit.core.source_code:
435            self._compilation_unit.core.add_source_code(name)
436        else:
437            lib_name = os.path.join("node_modules", name)
438            if os.path.isfile(lib_name) and name not in self._compilation_unit.core.source_code:
439                self._compilation_unit.core.add_source_code(lib_name)
440
441    # endregion
442    ###################################################################################
443    ###################################################################################
444    # region Analyze
445    ###################################################################################
446    ###################################################################################
447
448    @property
449    def parsed(self) -> bool:
450        return self._parsed
451
452    @property
453    def analyzed(self) -> bool:
454        return self._analyzed
455
456    def parse_contracts(self) -> None:
457        if not self._underlying_contract_to_parser:
458            logger.info(
459                f"No contracts were found in {self._compilation_unit.core.filename}, check the correct compilation"
460            )
461        if self._parsed:
462            raise Exception("Contract analysis can be run only once!")
463
464        def resolve_remapping_and_renaming(contract_parser: ContractSolc, want: str) -> Contract:
465            contract_name = contract_parser.remapping[want]
466            target = None
467            # For contracts that are imported and aliased e.g. 'import {A as B} from "./C.sol"',
468            # we look through the imports's (`Import`) renaming to find the original contract name
469            # and then look up the original contract in the import path's scope (`FileScope`).
470            for import_ in contract_parser.underlying_contract.file_scope.imports:
471                if contract_name in import_.renaming:
472                    target = self.compilation_unit.get_scope(
473                        import_.filename
474                    ).get_contract_from_name(import_.renaming[contract_name])
475
476            # Fallback to the current file scope if the contract is not found in the import path's scope.
477            # It is assumed that it isn't possible to defined a contract with the same name as "aliased" names.
478            if target is None:
479                target = contract_parser.underlying_contract.file_scope.get_contract_from_name(
480                    contract_name
481                )
482
483            if target == contract_parser.underlying_contract:
484                raise InheritanceResolutionError(
485                    "Could not resolve contract inheritance. This is likely caused by an import renaming that collides with existing names (see https://github.com/crytic/slither/issues/1758)."
486                    f"\n Try changing `contract {target}` ({target.source_mapping}) to a unique name as a workaround."
487                    "\n Please share the source code that caused this error here: https://github.com/crytic/slither/issues/"
488                )
489            assert target, f"Contract {contract_name} not found"
490            return target
491
492        # Update of the inheritance
493        for contract_parser in self._underlying_contract_to_parser.values():
494            ancestors = []
495            fathers = []
496            father_constructors = []
497            missing_inheritance = None
498
499            # Resolve linearized base contracts.
500            # Remove the first elem in linearizedBaseContracts as it is the contract itself.
501            for i in contract_parser.linearized_base_contracts[1:]:
502                if i in contract_parser.remapping:
503                    target = resolve_remapping_and_renaming(contract_parser, i)
504                    ancestors.append(target)
505                elif i in self._contracts_by_id:
506                    ancestors.append(self._contracts_by_id[i])
507                else:
508                    missing_inheritance = i
509
510            # Resolve immediate base contracts and attach references.
511            for i, src in contract_parser.baseContracts:
512                if i in contract_parser.remapping:
513                    target = resolve_remapping_and_renaming(contract_parser, i)
514                    fathers.append(target)
515                    target.add_reference_from_raw_source(src, self.compilation_unit)
516                elif i in self._contracts_by_id:
517                    target = self._contracts_by_id[i]
518                    fathers.append(target)
519                    target.add_reference_from_raw_source(src, self.compilation_unit)
520                else:
521                    missing_inheritance = i
522
523            # Resolve immediate base constructor calls.
524            for i in contract_parser.baseConstructorContractsCalled:
525                if i in contract_parser.remapping:
526                    target = resolve_remapping_and_renaming(contract_parser, i)
527                    father_constructors.append(target)
528                elif i in self._contracts_by_id:
529                    father_constructors.append(self._contracts_by_id[i])
530                else:
531                    missing_inheritance = i
532
533            contract_parser.underlying_contract.set_inheritance(
534                ancestors, fathers, father_constructors
535            )
536
537            if missing_inheritance:
538                self._compilation_unit.contracts_with_missing_inheritance.add(
539                    contract_parser.underlying_contract
540                )
541                txt = f"Missing inheritance {contract_parser.underlying_contract} ({contract_parser.compilation_unit.crytic_compile_compilation_unit.unique_id})\n"
542                txt += f"Missing inheritance ID: {missing_inheritance}\n"
543                if contract_parser.underlying_contract.inheritance:
544                    txt += "Inheritance found:\n"
545                    for contract_inherited in contract_parser.underlying_contract.inheritance:
546                        txt += f"\t - {contract_inherited} (ID {contract_inherited.id})\n"
547                contract_parser.log_incorrect_parsing(txt)
548
549                contract_parser.set_is_analyzed(True)
550                contract_parser.delete_content()
551
552        contracts_to_be_analyzed = list(self._underlying_contract_to_parser.values())
553
554        # Any contract can refer another contract enum without need for inheritance
555        self._analyze_all_enums(contracts_to_be_analyzed)
556
557        [c.set_is_analyzed(False) for c in self._underlying_contract_to_parser.values()]
558
559        libraries = [
560            c for c in contracts_to_be_analyzed if c.underlying_contract.contract_kind == "library"
561        ]
562        contracts_to_be_analyzed = [
563            c for c in contracts_to_be_analyzed if c.underlying_contract.contract_kind != "library"
564        ]
565
566        # We first parse the struct/variables/functions/contract
567        self._analyze_first_part(contracts_to_be_analyzed, libraries)
568
569        [c.set_is_analyzed(False) for c in self._underlying_contract_to_parser.values()]
570
571        # We analyze the struct and parse and analyze the events
572        # A contract can refer in the variables a struct or a event from any contract
573        # (without inheritance link)
574        self._analyze_second_part(contracts_to_be_analyzed, libraries)
575        [c.set_is_analyzed(False) for c in self._underlying_contract_to_parser.values()]
576
577        # Then we analyse state variables, functions and modifiers
578        self._analyze_third_part(contracts_to_be_analyzed, libraries)
579        [c.set_is_analyzed(False) for c in self._underlying_contract_to_parser.values()]
580
581        self._analyze_using_for(contracts_to_be_analyzed, libraries)
582
583        self._parsed = True
584
585    def analyze_contracts(self) -> None:
586        from slither.utils.timing import PhaseTimer
587
588        if not self._parsed:
589            raise SlitherException("Parse the contract before running analyses")
590
591        timer = PhaseTimer.get()
592
593        with timer.phase("convert_to_slithir"):
594            self._convert_to_slithir()
595
596        if not self._compilation_unit.core.skip_data_dependency:
597            with timer.phase("compute_dependency"):
598                compute_dependency(self._compilation_unit)
599
600        with timer.phase("compute_storage_layout"):
601            self._compilation_unit.compute_storage_layout()
602
603        self._analyzed = True
604
605    def _analyze_all_enums(self, contracts_to_be_analyzed: list[ContractSolc]) -> None:
606        while contracts_to_be_analyzed:
607            contract = contracts_to_be_analyzed[0]
608
609            contracts_to_be_analyzed = contracts_to_be_analyzed[1:]
610            all_father_analyzed = all(
611                self._underlying_contract_to_parser[father].is_analyzed
612                for father in contract.underlying_contract.inheritance
613            )
614
615            if not contract.underlying_contract.inheritance or all_father_analyzed:
616                self._analyze_enums(contract)
617            else:
618                contracts_to_be_analyzed += [contract]
619
620    def _analyze_first_part(
621        self,
622        contracts_to_be_analyzed: list[ContractSolc],
623        libraries: list[ContractSolc],
624    ) -> None:
625        for lib in libraries:
626            self._parse_struct_var_modifiers_functions(lib)
627
628        # Start with the contracts without inheritance
629        # Analyze a contract only if all its fathers
630        # Were analyzed
631        while contracts_to_be_analyzed:
632            contract = contracts_to_be_analyzed[0]
633
634            contracts_to_be_analyzed = contracts_to_be_analyzed[1:]
635            all_father_analyzed = all(
636                self._underlying_contract_to_parser[father].is_analyzed
637                for father in contract.underlying_contract.inheritance
638            )
639
640            if not contract.underlying_contract.inheritance or all_father_analyzed:
641                self._parse_struct_var_modifiers_functions(contract)
642
643            else:
644                contracts_to_be_analyzed += [contract]
645
646    def _analyze_second_part(
647        self,
648        contracts_to_be_analyzed: list[ContractSolc],
649        libraries: list[ContractSolc],
650    ) -> None:
651        for lib in libraries:
652            self._analyze_struct_events(lib)
653
654        self._analyze_top_level_variables()
655        self._analyze_top_level_structures()
656        self._analyze_top_level_events()
657
658        # Start with the contracts without inheritance
659        # Analyze a contract only if all its fathers
660        # Were analyzed
661        while contracts_to_be_analyzed:
662            contract = contracts_to_be_analyzed[0]
663
664            contracts_to_be_analyzed = contracts_to_be_analyzed[1:]
665            all_father_analyzed = all(
666                self._underlying_contract_to_parser[father].is_analyzed
667                for father in contract.underlying_contract.inheritance
668            )
669
670            if not contract.underlying_contract.inheritance or all_father_analyzed:
671                self._analyze_struct_events(contract)
672
673            else:
674                contracts_to_be_analyzed += [contract]
675
676    def _analyze_third_part(
677        self,
678        contracts_to_be_analyzed: list[ContractSolc],
679        libraries: list[ContractSolc],
680    ) -> None:
681        for lib in libraries:
682            self._analyze_variables_modifiers_functions(lib)
683
684        # Start with the contracts without inheritance
685        # Analyze a contract only if all its fathers
686        # Were analyzed
687        while contracts_to_be_analyzed:
688            contract = contracts_to_be_analyzed[0]
689
690            contracts_to_be_analyzed = contracts_to_be_analyzed[1:]
691            all_father_analyzed = all(
692                self._underlying_contract_to_parser[father].is_analyzed
693                for father in contract.underlying_contract.inheritance
694            )
695
696            if not contract.underlying_contract.inheritance or all_father_analyzed:
697                self._analyze_variables_modifiers_functions(contract)
698
699            else:
700                contracts_to_be_analyzed += [contract]
701
702    def _analyze_using_for(
703        self, contracts_to_be_analyzed: list[ContractSolc], libraries: list[ContractSolc]
704    ) -> None:
705        self._analyze_top_level_using_for()
706
707        for lib in libraries:
708            lib.analyze_using_for()
709
710        while contracts_to_be_analyzed:
711            contract = contracts_to_be_analyzed[0]
712
713            contracts_to_be_analyzed = contracts_to_be_analyzed[1:]
714            all_father_analyzed = all(
715                self._underlying_contract_to_parser[father].is_analyzed
716                for father in contract.underlying_contract.inheritance
717            )
718
719            if not contract.underlying_contract.inheritance or all_father_analyzed:
720                contract.analyze_using_for()
721                contract.set_is_analyzed(True)
722            else:
723                contracts_to_be_analyzed += [contract]
724
725    def _analyze_enums(self, contract: ContractSolc) -> None:
726        # Enum must be analyzed first
727        contract.analyze_enums()
728        contract.set_is_analyzed(True)
729
730    def _parse_struct_var_modifiers_functions(self, contract: ContractSolc) -> None:
731        contract.parse_structs()  # struct can refer another struct
732        contract.parse_state_variables()
733        contract.parse_modifiers()
734        contract.parse_functions()
735        contract.parse_custom_errors()
736        contract.parse_type_alias()
737        contract.set_is_analyzed(True)
738
739    def _analyze_struct_events(self, contract: ContractSolc) -> None:
740        contract.analyze_constant_state_variables()
741
742        # Struct can refer to enum, or state variables
743        contract.analyze_structs()
744        # Event can refer to struct
745        contract.analyze_events()
746
747        contract.analyze_custom_errors()
748
749        # Must be here since it can use top level variables hence they must be analyzed
750        contract.analyze_storage_layout()
751
752        contract.set_is_analyzed(True)
753
754    def _analyze_top_level_structures(self) -> None:
755        try:
756            for struct in self._structures_top_level_parser:
757                struct.analyze()
758        except (VariableNotFound, KeyError) as e:
759            raise SlitherException(f"Missing struct {e} during top level structure analyze") from e
760
761    def _analyze_top_level_variables(self) -> None:
762        try:
763            for var in self._variables_top_level_parser:
764                var.analyze(var)
765        except (VariableNotFound, KeyError) as e:
766            raise SlitherException(f"Missing {e} during variable analyze") from e
767
768    def _analyze_top_level_events(self) -> None:
769        try:
770            for event in self._events_top_level_parser:
771                event.analyze()
772        except (VariableNotFound, KeyError) as e:
773            raise SlitherException(f"Missing event {e} during top level event analyze") from e
774
775    def _analyze_params_top_level_function(self) -> None:
776        for func_parser in self._functions_top_level_parser:
777            func_parser.analyze_params()
778            self._compilation_unit.add_function(func_parser.underlying_function)
779
780    def _analyze_top_level_using_for(self) -> None:
781        for using_for in self._using_for_top_level_parser:
782            using_for.analyze()
783
784    def _analyze_params_custom_error(self) -> None:
785        for custom_error_parser in self._custom_error_parser:
786            custom_error_parser.analyze_params()
787
788    def _analyze_content_top_level_function(self) -> None:
789        try:
790            for func_parser in self._functions_top_level_parser:
791                func_parser.analyze_content()
792        except (VariableNotFound, KeyError) as e:
793            raise SlitherException(f"Missing {e} during top level function analyze") from e
794
795    def _analyze_variables_modifiers_functions(self, contract: ContractSolc) -> None:
796        # State variables, modifiers and functions can refer to anything
797
798        contract.analyze_params_modifiers()
799        contract.analyze_params_functions()
800        self._analyze_params_top_level_function()
801        self._analyze_params_custom_error()
802
803        contract.analyze_state_variables()
804
805        contract.analyze_content_modifiers()
806        contract.analyze_content_functions()
807        self._analyze_content_top_level_function()
808
809        contract.set_is_analyzed(True)
810
811    def _convert_to_slithir(self) -> None:
812        for contract in self._compilation_unit.contracts:
813            contract.add_constructor_variables()
814
815            for func in contract.functions + contract.modifiers:
816                try:
817                    func.generate_slithir_and_analyze()
818
819                except AttributeError as e:
820                    # This can happens for example if there is a call to an interface
821                    # And the interface is redefined due to contract's name reuse
822                    # But the available version misses some functions
823                    self._underlying_contract_to_parser[contract].log_incorrect_parsing(
824                        f"Impossible to generate IR for {contract.name}.{func.name} ({func.source_mapping}):\n {e}"
825                    )
826                except Exception as e:
827                    func_expressions = "\n".join([f"\t{ex}" for ex in func.expressions])
828                    logger.error(
829                        f"\nFailed to generate IR for {contract.name}.{func.name}. Please open an issue https://github.com/crytic/slither/issues.\n{contract.name}.{func.name} ({func.source_mapping}):\n "
830                        f"{func_expressions}"
831                    )
832                    raise e
833            try:
834                contract.convert_expression_to_slithir_ssa()
835            except Exception as e:
836                logger.error(
837                    f"\nFailed to convert IR to SSA for {contract.name} contract. Please open an issue https://github.com/crytic/slither/issues.\n "
838                )
839                raise e
840
841        for func in self._compilation_unit.functions_top_level:
842            try:
843                func.generate_slithir_and_analyze()
844            except AttributeError as e:
845                logger.error(
846                    f"Impossible to generate IR for top level function {func.name} ({func.source_mapping}):\n {e}"
847                )
848            except Exception as e:
849                func_expressions = "\n".join([f"\t{ex}" for ex in func.expressions])
850                logger.error(
851                    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 "
852                    f"{func_expressions}"
853                )
854                raise e
855
856            try:
857                func.generate_slithir_ssa({})
858            except Exception as e:
859                func_expressions = "\n".join([f"\t{ex}" for ex in func.expressions])
860                logger.error(
861                    f"\nFailed to convert IR to SSA for top level function {func.name}. Please open an issue https://github.com/crytic/slither/issues.\n{func.name} ({func.source_mapping}):\n "
862                    f"{func_expressions}"
863                )
864                raise e
865
866        self._compilation_unit.propagate_function_calls()
867        for contract in self._compilation_unit.contracts:
868            contract.fix_phi()
869            contract.update_read_write_using_ssa()
870
871    # endregion

This class is inherited by all the declarations class that can be used in the expression/type parsing As a source of context/scope

It is used by any declaration class that can be top-level and require complex parsing

SlitherCompilationUnitSolc( compilation_unit: slither.core.compilation_unit.SlitherCompilationUnit)
 76    def __init__(self, compilation_unit: SlitherCompilationUnit) -> None:
 77        super().__init__()
 78
 79        self._compilation_unit: SlitherCompilationUnit = compilation_unit
 80
 81        self._contracts_by_id: dict[int, Contract] = {}
 82        # For top level functions, there should only be one `Function` since they can't be virtual and therefore can't be overridden.
 83        self._functions_by_id: dict[int, list[Function]] = defaultdict(list)
 84        self.imports_by_id: dict[int, Import] = {}
 85        self.top_level_events_by_id: dict[int, EventTopLevel] = {}
 86        self.top_level_errors_by_id: dict[int, EventTopLevel] = {}
 87        self.top_level_structures_by_id: dict[int, StructureTopLevel] = {}
 88        self.top_level_variables_by_id: dict[int, TopLevelVariable] = {}
 89        self.top_level_type_aliases_by_id: dict[int, TypeAliasTopLevel] = {}
 90        self.top_level_enums_by_id: dict[int, EnumTopLevel] = {}
 91
 92        self._parsed = False
 93        self._analyzed = False
 94        self._is_compact_ast = False
 95
 96        self._underlying_contract_to_parser: dict[Contract, ContractSolc] = {}
 97        self._structures_top_level_parser: list[StructureTopLevelSolc] = []
 98        self._custom_error_parser: list[CustomErrorSolc] = []
 99        self._variables_top_level_parser: list[TopLevelVariableSolc] = []
100        self._functions_top_level_parser: list[FunctionSolc] = []
101        self._using_for_top_level_parser: list[UsingForTopLevelSolc] = []
102        self._events_top_level_parser: list[EventTopLevelSolc] = []
103        self._all_functions_and_modifier_parser: list[FunctionSolc] = []
104
105        self._top_level_contracts_counter = 0
top_level_events_by_id: dict[int, slither.core.declarations.event_top_level.EventTopLevel]
top_level_errors_by_id: dict[int, slither.core.declarations.event_top_level.EventTopLevel]
top_level_variables_by_id: dict[int, slither.core.variables.top_level_variable.TopLevelVariable]
top_level_type_aliases_by_id: dict[int, slither.core.solidity_types.type_alias.TypeAliasTopLevel]
top_level_enums_by_id: dict[int, slither.core.declarations.enum_top_level.EnumTopLevel]
107    @property
108    def compilation_unit(self) -> SlitherCompilationUnit:
109        return self._compilation_unit
all_functions_and_modifiers_parser: list[slither.solc_parsing.declarations.function.FunctionSolc]
111    @property
112    def all_functions_and_modifiers_parser(self) -> list[FunctionSolc]:
113        return self._all_functions_and_modifier_parser
def add_function_or_modifier_parser(self, f: slither.solc_parsing.declarations.function.FunctionSolc) -> None:
115    def add_function_or_modifier_parser(self, f: FunctionSolc) -> None:
116        self._all_functions_and_modifier_parser.append(f)
117        self._functions_by_id[f.underlying_function.id].append(f.underlying_function)
119    @property
120    def underlying_contract_to_parser(self) -> dict[Contract, ContractSolc]:
121        return self._underlying_contract_to_parser
slither_parser: SlitherCompilationUnitSolc
123    @property
124    def slither_parser(self) -> "SlitherCompilationUnitSolc":
125        return self
contracts_by_id: dict[int, slither.core.declarations.contract.Contract]
127    @property
128    def contracts_by_id(self) -> dict[int, Contract]:
129        return self._contracts_by_id
functions_by_id: dict[int, list[slither.core.declarations.function.Function]]
131    @property
132    def functions_by_id(self) -> dict[int, list[Function]]:
133        return self._functions_by_id
def get_key(self) -> str:
141    def get_key(self) -> str:
142        if self._is_compact_ast:
143            return "nodeType"
144        return "name"
def get_children(self) -> str:
146    def get_children(self) -> str:
147        if self._is_compact_ast:
148            return "nodes"
149        return "children"
is_compact_ast: bool
151    @property
152    def is_compact_ast(self) -> bool:
153        return self._is_compact_ast
def parse_top_level_from_json(self, json_data: str) -> bool:
162    def parse_top_level_from_json(self, json_data: str) -> bool:
163        try:
164            data_loaded = json.loads(json_data)
165            # Truffle AST
166            if "ast" in data_loaded:
167                self.parse_top_level_items(data_loaded["ast"], data_loaded["sourcePath"])
168                return True
169            # solc AST, where the non-json text was removed
170            if "attributes" in data_loaded:
171                filename = data_loaded["attributes"]["absolutePath"]
172            else:
173                filename = data_loaded["absolutePath"]
174            self.parse_top_level_items(data_loaded, filename)
175            return True
176        except ValueError:
177            first = json_data.find("{")
178            if first != -1:
179                last = json_data.rfind("}") + 1
180                filename = json_data[0:first]
181                json_data = json_data[first:last]
182
183                data_loaded = json.loads(json_data)
184                self.parse_top_level_items(data_loaded, filename)
185                return True
186            return False
def parse_top_level_items(self, data_loaded: dict, filename: str) -> None:
219    def parse_top_level_items(self, data_loaded: dict, filename: str) -> None:
220        if not data_loaded or data_loaded is None:
221            logger.error(
222                "crytic-compile returned an empty AST. "
223                "If you are trying to analyze a contract from etherscan or similar make sure it has source code available."
224            )
225            return
226
227        exported_symbols = {}
228        if "nodeType" in data_loaded:
229            self._is_compact_ast = True
230            exported_symbols = data_loaded.get("exportedSymbols", {})
231        else:
232            attributes = data_loaded.get("attributes", {})
233            exported_symbols = attributes.get("exportedSymbols", {})
234
235        if "sourcePaths" in data_loaded:
236            for sourcePath in data_loaded["sourcePaths"]:
237                if os.path.isfile(sourcePath):
238                    self._compilation_unit.core.add_source_code(sourcePath)
239
240        if data_loaded[self.get_key()] == "root":
241            logger.error("solc <0.4 is not supported")
242            return
243        if data_loaded[self.get_key()] == "SourceUnit":
244            self._parse_source_unit(data_loaded, filename)
245        else:
246            logger.error("solc version is not supported")
247            return
248
249        if self.get_children() not in data_loaded:
250            return
251
252        scope = self.compilation_unit.get_scope(filename)
253        # Exported symbols includes a reference ID to all top-level definitions the file exports,
254        # including def's brought in by imports (even transitively) and def's local to the file.
255        for refId in exported_symbols.values():
256            scope.exported_symbols |= set(refId)
257
258        for top_level_data in data_loaded[self.get_children()]:
259            if top_level_data[self.get_key()] == "ContractDefinition":
260                contract = Contract(self._compilation_unit, scope)
261                contract_parser = ContractSolc(self, contract, top_level_data)
262                scope.contracts[contract.name] = contract
263                if "src" in top_level_data:
264                    contract.set_offset(top_level_data["src"], self._compilation_unit)
265
266                self._underlying_contract_to_parser[contract] = contract_parser
267
268            elif top_level_data[self.get_key()] == "PragmaDirective":
269                if self._is_compact_ast:
270                    pragma = Pragma(top_level_data["literals"], scope)
271                    scope.pragmas.add(pragma)
272                else:
273                    pragma = Pragma(top_level_data["attributes"]["literals"], scope)
274                    scope.pragmas.add(pragma)
275                pragma.set_offset(top_level_data["src"], self._compilation_unit)
276                self._compilation_unit.pragma_directives.append(pragma)
277
278            elif top_level_data[self.get_key()] == "UsingForDirective":
279                scope = self.compilation_unit.get_scope(filename)
280                usingFor = UsingForTopLevel(scope)
281                usingFor_parser = UsingForTopLevelSolc(usingFor, top_level_data, self)
282                usingFor.set_offset(top_level_data["src"], self._compilation_unit)
283                scope.using_for_directives.add(usingFor)
284
285                self._compilation_unit.using_for_top_level.append(usingFor)
286                self._using_for_top_level_parser.append(usingFor_parser)
287
288            elif top_level_data[self.get_key()] == "ImportDirective":
289                referenceId = top_level_data["id"]
290                if self.is_compact_ast:
291                    import_directive = Import(
292                        Path(
293                            top_level_data["absolutePath"],
294                        ),
295                        scope,
296                    )
297                    scope.imports.add(import_directive)
298                    # TODO investigate unitAlias in version < 0.7 and legacy ast
299                    if "unitAlias" in top_level_data:
300                        import_directive.alias = top_level_data["unitAlias"]
301                    if "symbolAliases" in top_level_data:
302                        symbol_aliases = top_level_data["symbolAliases"]
303                        _handle_import_aliases(symbol_aliases, import_directive, scope)
304                else:
305                    import_directive = Import(
306                        Path(
307                            top_level_data["attributes"].get("absolutePath", ""),
308                        ),
309                        scope,
310                    )
311                    scope.imports.add(import_directive)
312                    # TODO investigate unitAlias in version < 0.7 and legacy ast
313                    if (
314                        "attributes" in top_level_data
315                        and "unitAlias" in top_level_data["attributes"]
316                    ):
317                        import_directive.alias = top_level_data["attributes"]["unitAlias"]
318                import_directive.set_offset(top_level_data["src"], self._compilation_unit)
319                self._compilation_unit.import_directives.append(import_directive)
320                self.imports_by_id[referenceId] = import_directive
321
322                get_imported_scope = self.compilation_unit.get_scope(import_directive.filename)
323                scope.accessible_scopes.append(get_imported_scope)
324
325            elif top_level_data[self.get_key()] == "StructDefinition":
326                st = StructureTopLevel(self.compilation_unit, scope)
327                st.set_offset(top_level_data["src"], self._compilation_unit)
328                st_parser = StructureTopLevelSolc(st, top_level_data, self)
329                scope.structures[st.name] = st
330
331                self._compilation_unit.structures_top_level.append(st)
332                self._structures_top_level_parser.append(st_parser)
333                referenceId = top_level_data["id"]
334                self.top_level_structures_by_id[referenceId] = st
335
336            elif top_level_data[self.get_key()] == "EnumDefinition":
337                # Note enum don't need a complex parser, so everything is directly done
338                self._parse_enum(top_level_data, filename)
339
340            elif top_level_data[self.get_key()] == "VariableDeclaration":
341                var = TopLevelVariable(scope)
342                var_parser = TopLevelVariableSolc(var, top_level_data, self)
343                var.set_offset(top_level_data["src"], self._compilation_unit)
344
345                self._compilation_unit.variables_top_level.append(var)
346                self._variables_top_level_parser.append(var_parser)
347                scope.variables[var.name] = var
348                referenceId = top_level_data["id"]
349                self.top_level_variables_by_id[referenceId] = var
350
351            elif top_level_data[self.get_key()] == "FunctionDefinition":
352                func = FunctionTopLevel(self._compilation_unit, scope)
353                scope.functions.add(func)
354                func.set_offset(top_level_data["src"], self._compilation_unit)
355                func_parser = FunctionSolc(func, top_level_data, None, self)
356
357                self._compilation_unit.functions_top_level.append(func)
358                self._functions_top_level_parser.append(func_parser)
359                self.add_function_or_modifier_parser(func_parser)
360
361            elif top_level_data[self.get_key()] == "ErrorDefinition":
362                custom_error = CustomErrorTopLevel(self._compilation_unit, scope)
363                custom_error.set_offset(top_level_data["src"], self._compilation_unit)
364
365                custom_error_parser = CustomErrorSolc(custom_error, top_level_data, None, self)
366                scope.custom_errors.add(custom_error)
367                self._compilation_unit.custom_errors.append(custom_error)
368                self._custom_error_parser.append(custom_error_parser)
369                referenceId = top_level_data["id"]
370                self.top_level_errors_by_id[referenceId] = custom_error
371
372            elif top_level_data[self.get_key()] == "UserDefinedValueTypeDefinition":
373                assert "name" in top_level_data
374                alias = top_level_data["name"]
375                assert "underlyingType" in top_level_data
376                underlying_type = top_level_data["underlyingType"]
377                assert (
378                    "nodeType" in underlying_type
379                    and underlying_type["nodeType"] == "ElementaryTypeName"
380                )
381                assert "name" in underlying_type
382
383                original_type = ElementaryType(underlying_type["name"])
384
385                type_alias = TypeAliasTopLevel(original_type, alias, scope)
386                type_alias.set_offset(top_level_data["src"], self._compilation_unit)
387                self._compilation_unit.type_aliases[alias] = type_alias
388                scope.type_aliases[alias] = type_alias
389                referenceId = top_level_data["id"]
390                self.top_level_type_aliases_by_id[referenceId] = type_alias
391
392            elif top_level_data[self.get_key()] == "EventDefinition":
393                event = EventTopLevel(scope)
394                event.set_offset(top_level_data["src"], self._compilation_unit)
395
396                event_parser = EventTopLevelSolc(event, top_level_data, self)  # type: ignore
397                self._events_top_level_parser.append(event_parser)
398                scope.events.add(event)
399                self._compilation_unit.events_top_level.append(event)
400                referenceId = top_level_data["id"]
401                self.top_level_events_by_id[referenceId] = event
402
403            else:
404                raise SlitherException(f"Top level {top_level_data[self.get_key()]} not supported")
parsed: bool
448    @property
449    def parsed(self) -> bool:
450        return self._parsed
analyzed: bool
452    @property
453    def analyzed(self) -> bool:
454        return self._analyzed
def parse_contracts(self) -> None:
456    def parse_contracts(self) -> None:
457        if not self._underlying_contract_to_parser:
458            logger.info(
459                f"No contracts were found in {self._compilation_unit.core.filename}, check the correct compilation"
460            )
461        if self._parsed:
462            raise Exception("Contract analysis can be run only once!")
463
464        def resolve_remapping_and_renaming(contract_parser: ContractSolc, want: str) -> Contract:
465            contract_name = contract_parser.remapping[want]
466            target = None
467            # For contracts that are imported and aliased e.g. 'import {A as B} from "./C.sol"',
468            # we look through the imports's (`Import`) renaming to find the original contract name
469            # and then look up the original contract in the import path's scope (`FileScope`).
470            for import_ in contract_parser.underlying_contract.file_scope.imports:
471                if contract_name in import_.renaming:
472                    target = self.compilation_unit.get_scope(
473                        import_.filename
474                    ).get_contract_from_name(import_.renaming[contract_name])
475
476            # Fallback to the current file scope if the contract is not found in the import path's scope.
477            # It is assumed that it isn't possible to defined a contract with the same name as "aliased" names.
478            if target is None:
479                target = contract_parser.underlying_contract.file_scope.get_contract_from_name(
480                    contract_name
481                )
482
483            if target == contract_parser.underlying_contract:
484                raise InheritanceResolutionError(
485                    "Could not resolve contract inheritance. This is likely caused by an import renaming that collides with existing names (see https://github.com/crytic/slither/issues/1758)."
486                    f"\n Try changing `contract {target}` ({target.source_mapping}) to a unique name as a workaround."
487                    "\n Please share the source code that caused this error here: https://github.com/crytic/slither/issues/"
488                )
489            assert target, f"Contract {contract_name} not found"
490            return target
491
492        # Update of the inheritance
493        for contract_parser in self._underlying_contract_to_parser.values():
494            ancestors = []
495            fathers = []
496            father_constructors = []
497            missing_inheritance = None
498
499            # Resolve linearized base contracts.
500            # Remove the first elem in linearizedBaseContracts as it is the contract itself.
501            for i in contract_parser.linearized_base_contracts[1:]:
502                if i in contract_parser.remapping:
503                    target = resolve_remapping_and_renaming(contract_parser, i)
504                    ancestors.append(target)
505                elif i in self._contracts_by_id:
506                    ancestors.append(self._contracts_by_id[i])
507                else:
508                    missing_inheritance = i
509
510            # Resolve immediate base contracts and attach references.
511            for i, src in contract_parser.baseContracts:
512                if i in contract_parser.remapping:
513                    target = resolve_remapping_and_renaming(contract_parser, i)
514                    fathers.append(target)
515                    target.add_reference_from_raw_source(src, self.compilation_unit)
516                elif i in self._contracts_by_id:
517                    target = self._contracts_by_id[i]
518                    fathers.append(target)
519                    target.add_reference_from_raw_source(src, self.compilation_unit)
520                else:
521                    missing_inheritance = i
522
523            # Resolve immediate base constructor calls.
524            for i in contract_parser.baseConstructorContractsCalled:
525                if i in contract_parser.remapping:
526                    target = resolve_remapping_and_renaming(contract_parser, i)
527                    father_constructors.append(target)
528                elif i in self._contracts_by_id:
529                    father_constructors.append(self._contracts_by_id[i])
530                else:
531                    missing_inheritance = i
532
533            contract_parser.underlying_contract.set_inheritance(
534                ancestors, fathers, father_constructors
535            )
536
537            if missing_inheritance:
538                self._compilation_unit.contracts_with_missing_inheritance.add(
539                    contract_parser.underlying_contract
540                )
541                txt = f"Missing inheritance {contract_parser.underlying_contract} ({contract_parser.compilation_unit.crytic_compile_compilation_unit.unique_id})\n"
542                txt += f"Missing inheritance ID: {missing_inheritance}\n"
543                if contract_parser.underlying_contract.inheritance:
544                    txt += "Inheritance found:\n"
545                    for contract_inherited in contract_parser.underlying_contract.inheritance:
546                        txt += f"\t - {contract_inherited} (ID {contract_inherited.id})\n"
547                contract_parser.log_incorrect_parsing(txt)
548
549                contract_parser.set_is_analyzed(True)
550                contract_parser.delete_content()
551
552        contracts_to_be_analyzed = list(self._underlying_contract_to_parser.values())
553
554        # Any contract can refer another contract enum without need for inheritance
555        self._analyze_all_enums(contracts_to_be_analyzed)
556
557        [c.set_is_analyzed(False) for c in self._underlying_contract_to_parser.values()]
558
559        libraries = [
560            c for c in contracts_to_be_analyzed if c.underlying_contract.contract_kind == "library"
561        ]
562        contracts_to_be_analyzed = [
563            c for c in contracts_to_be_analyzed if c.underlying_contract.contract_kind != "library"
564        ]
565
566        # We first parse the struct/variables/functions/contract
567        self._analyze_first_part(contracts_to_be_analyzed, libraries)
568
569        [c.set_is_analyzed(False) for c in self._underlying_contract_to_parser.values()]
570
571        # We analyze the struct and parse and analyze the events
572        # A contract can refer in the variables a struct or a event from any contract
573        # (without inheritance link)
574        self._analyze_second_part(contracts_to_be_analyzed, libraries)
575        [c.set_is_analyzed(False) for c in self._underlying_contract_to_parser.values()]
576
577        # Then we analyse state variables, functions and modifiers
578        self._analyze_third_part(contracts_to_be_analyzed, libraries)
579        [c.set_is_analyzed(False) for c in self._underlying_contract_to_parser.values()]
580
581        self._analyze_using_for(contracts_to_be_analyzed, libraries)
582
583        self._parsed = True
def analyze_contracts(self) -> None:
585    def analyze_contracts(self) -> None:
586        from slither.utils.timing import PhaseTimer
587
588        if not self._parsed:
589            raise SlitherException("Parse the contract before running analyses")
590
591        timer = PhaseTimer.get()
592
593        with timer.phase("convert_to_slithir"):
594            self._convert_to_slithir()
595
596        if not self._compilation_unit.core.skip_data_dependency:
597            with timer.phase("compute_dependency"):
598                compute_dependency(self._compilation_unit)
599
600        with timer.phase("compute_storage_layout"):
601            self._compilation_unit.compute_storage_layout()
602
603        self._analyzed = True