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