slither.solc_parsing.slither_compilation_unit_solc

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

Common base class for all non-exit exceptions.

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