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)>
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
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:
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")
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