slither.core.declarations.function
Function module
1""" 2Function module 3""" 4 5import logging 6from abc import abstractmethod, ABCMeta 7from collections import namedtuple 8from enum import Enum 9from itertools import groupby 10from typing import Any, TYPE_CHECKING, Optional, Union 11from collections.abc import Callable 12 13from slither.core.cfg.scope import Scope 14from slither.core.declarations.solidity_variables import ( 15 SolidityFunction, 16 SolidityVariable, 17 SolidityVariableComposed, 18) 19from slither.core.expressions import ( 20 Identifier, 21 IndexAccess, 22 MemberAccess, 23 UnaryOperation, 24) 25from slither.core.solidity_types.type import Type 26from slither.core.source_mapping.source_mapping import SourceMapping 27from slither.core.variables.local_variable import LocalVariable 28from slither.core.variables.state_variable import StateVariable 29from slither.utils.type import convert_type_for_solidity_signature_to_string 30from slither.utils.utils import unroll 31 32 33if TYPE_CHECKING: 34 from slither.core.declarations import Contract, FunctionContract 35 from slither.core.cfg.node import Node, NodeType 36 from slither.core.variables.variable import Variable 37 from slither.slithir.variables.variable import SlithIRVariable 38 from slither.slithir.variables import LocalIRVariable 39 from slither.core.expressions.expression import Expression 40 from slither.slithir.operations import ( 41 HighLevelCall, 42 InternalCall, 43 LibraryCall, 44 LowLevelCall, 45 SolidityCall, 46 Operation, 47 ) 48 from slither.core.compilation_unit import SlitherCompilationUnit 49 from slither.core.scope.scope import FileScope 50 51LOGGER = logging.getLogger("Function") 52ReacheableNode = namedtuple("ReacheableNode", ["node", "ir"]) 53 54 55class ModifierStatements: 56 def __init__( 57 self, 58 modifier: Union["Contract", "Function"], 59 entry_point: "Node", 60 nodes: list["Node"], 61 ) -> None: 62 self._modifier = modifier 63 self._entry_point = entry_point 64 self._nodes = nodes 65 66 @property 67 def modifier(self) -> Union["Contract", "Function"]: 68 return self._modifier 69 70 @property 71 def entry_point(self) -> "Node": 72 return self._entry_point 73 74 @entry_point.setter 75 def entry_point(self, entry_point: "Node"): 76 self._entry_point = entry_point 77 78 @property 79 def nodes(self) -> list["Node"]: 80 return self._nodes 81 82 @nodes.setter 83 def nodes(self, nodes: list["Node"]): 84 self._nodes = nodes 85 86 87class FunctionType(Enum): 88 NORMAL = 0 89 CONSTRUCTOR = 1 90 FALLBACK = 2 91 RECEIVE = 3 92 CONSTRUCTOR_VARIABLES = 10 # Fake function to hold variable declaration statements 93 CONSTRUCTOR_CONSTANT_VARIABLES = 11 # Fake function to hold variable declaration statements 94 95 96def _filter_state_variables_written(expressions: list["Expression"]): 97 ret = [] 98 99 for expression in expressions: 100 if isinstance(expression, (Identifier, UnaryOperation, MemberAccess)): 101 ret.append(expression.expression) 102 elif isinstance(expression, IndexAccess): 103 ret.append(expression.expression_left) 104 return ret 105 106 107class FunctionLanguage(Enum): 108 Solidity = 0 109 Yul = 1 110 Vyper = 2 111 112 113class Function(SourceMapping, metaclass=ABCMeta): 114 """ 115 Function class 116 """ 117 118 def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None: 119 super().__init__() 120 self._internal_scope: list[str] = [] 121 self._name: str | None = None 122 self._view: bool = False 123 self._pure: bool = False 124 self._payable: bool = False 125 self._visibility: str | None = None 126 self._virtual: bool = False 127 self._overrides: list[FunctionContract] = [] 128 self._overridden_by: list[FunctionContract] = [] 129 130 self._is_implemented: bool | None = None 131 self._is_empty: bool | None = None 132 self._entry_point: Node | None = None 133 self._nodes: list[Node] = [] 134 self._variables: dict[str, LocalVariable] = {} 135 # slithir Temporary and references variables (but not SSA) 136 self._slithir_variables: set[SlithIRVariable] = set() 137 self._parameters: list[LocalVariable] = [] 138 self._parameters_ssa: list[LocalIRVariable] = [] 139 self._parameters_src: SourceMapping = SourceMapping() 140 # This is used for vyper calls with default arguments 141 self._default_args_as_expressions: list[Expression] = [] 142 self._returns: list[LocalVariable] = [] 143 self._returns_ssa: list[LocalIRVariable] = [] 144 self._returns_src: SourceMapping = SourceMapping() 145 self._return_values: list[SlithIRVariable] | None = None 146 self._return_values_ssa: list[SlithIRVariable] | None = None 147 self._vars_read: list[Variable] = [] 148 self._vars_written: list[Variable] = [] 149 self._state_vars_read: list[StateVariable] = [] 150 self._vars_read_or_written: list[Variable] = [] 151 self._solidity_vars_read: list[SolidityVariable] = [] 152 self._state_vars_written: list[StateVariable] = [] 153 self._internal_calls: list[InternalCall] = [] 154 self._solidity_calls: list[SolidityCall] = [] 155 self._low_level_calls: list[LowLevelCall] = [] 156 self._high_level_calls: list[tuple[Contract, HighLevelCall]] = [] 157 self._library_calls: list[LibraryCall] = [] 158 self._external_calls_as_expressions: list[Expression] = [] 159 self._expression_vars_read: list[Expression] = [] 160 self._expression_vars_written: list[Expression] = [] 161 self._expression_calls: list[Expression] = [] 162 # self._expression_modifiers: List["Expression"] = [] 163 self._modifiers: list[ModifierStatements] = [] 164 self._explicit_base_constructor_calls: list[ModifierStatements] = [] 165 self._contains_assembly: bool = False 166 167 self._expressions: list[Expression] | None = None 168 self._slithir_operations: list[Operation] | None = None 169 self._slithir_ssa_operations: list[Operation] | None = None 170 171 self._all_expressions: list[Expression] | None = None 172 self._all_slithir_operations: list[Operation] | None = None 173 self._all_internals_calls: list[InternalCall] | None = None 174 self._all_high_level_calls: list[tuple[Contract, HighLevelCall]] | None = None 175 self._all_library_calls: list[LibraryCall] | None = None 176 self._all_low_level_calls: list[LowLevelCall] | None = None 177 self._all_solidity_calls: list[SolidityCall] | None = None 178 self._all_variables_read: list[Variable] | None = None 179 self._all_variables_written: list[Variable] | None = None 180 self._all_state_variables_read: list[StateVariable] | None = None 181 self._all_solidity_variables_read: list[SolidityVariable] | None = None 182 self._all_state_variables_written: list[StateVariable] | None = None 183 self._all_slithir_variables: list[SlithIRVariable] | None = None 184 self._all_nodes: list[Node] | None = None 185 self._all_conditional_state_variables_read: list[StateVariable] | None = None 186 self._all_conditional_state_variables_read_with_loop: list[StateVariable] | None = None 187 self._all_conditional_solidity_variables_read: list[SolidityVariable] | None = None 188 self._all_conditional_solidity_variables_read_with_loop: list[SolidityVariable] | None = ( 189 None 190 ) 191 self._all_solidity_variables_used_as_args: list[SolidityVariable] | None = None 192 193 self._is_shadowed: bool = False 194 self._shadows: bool = False 195 196 # set(ReacheableNode) 197 self._reachable_from_nodes: set[ReacheableNode] = set() 198 self._reachable_from_functions: set[Function] = set() 199 self._all_reachable_from_functions: set[Function] | None = None 200 201 # Constructor, fallback, State variable constructor 202 self._function_type: FunctionType | None = None 203 self._is_constructor: bool | None = None 204 205 # Computed on the fly, can be True of False 206 self._can_reenter: bool | None = None 207 self._can_send_eth: bool | None = None 208 209 self._nodes_ordered_dominators: list[Node] | None = None 210 211 self._counter_nodes = 0 212 213 # Memoize parameters: 214 # TODO: identify all the memoize parameters and add a way to undo the memoization 215 self._full_name: str | None = None 216 self._signature: tuple[str, list[str], list[str]] | None = None 217 self._solidity_signature: str | None = None 218 self._signature_str: str | None = None 219 self._canonical_name: str | None = None 220 self._is_protected: bool | None = None 221 222 self.compilation_unit: SlitherCompilationUnit = compilation_unit 223 224 self.function_language: FunctionLanguage = ( 225 FunctionLanguage.Solidity if compilation_unit.is_solidity else FunctionLanguage.Vyper 226 ) 227 228 self._id: str | None = None 229 230 # To be improved with a parsing of the documentation 231 self.has_documentation: bool = False 232 233 ################################################################################### 234 ################################################################################### 235 # region General properties 236 ################################################################################### 237 ################################################################################### 238 239 @property 240 def name(self) -> str: 241 """ 242 str: function name 243 """ 244 if self._name == "" and self._function_type == FunctionType.CONSTRUCTOR: 245 return "constructor" 246 if self._name == "" and self._function_type == FunctionType.FALLBACK: 247 return "fallback" 248 if self._function_type == FunctionType.RECEIVE: 249 return "receive" 250 if self._function_type == FunctionType.CONSTRUCTOR_VARIABLES: 251 return "slitherConstructorVariables" 252 if self._function_type == FunctionType.CONSTRUCTOR_CONSTANT_VARIABLES: 253 return "slitherConstructorConstantVariables" 254 return self._name 255 256 @name.setter 257 def name(self, new_name: str): 258 self._name = new_name 259 260 @property 261 def internal_scope(self) -> list[str]: 262 """ 263 Return a list of name representing the scope of the function 264 This is used to model nested functions declared in YUL 265 266 :return: 267 """ 268 return self._internal_scope 269 270 @internal_scope.setter 271 def internal_scope(self, new_scope: list[str]): 272 self._internal_scope = new_scope 273 274 @property 275 def full_name(self) -> str: 276 """ 277 str: func_name(type1,type2) 278 Return the function signature without the return values 279 The difference between this function and solidity_function is that full_name does not translate the underlying 280 type (ex: structure, contract to address, ...) 281 """ 282 if self._full_name is None: 283 name, parameters, _ = self.signature 284 full_name = ".".join(self._internal_scope + [name]) + "(" + ",".join(parameters) + ")" 285 self._full_name = full_name 286 return self._full_name 287 288 @property 289 @abstractmethod 290 def canonical_name(self) -> str: 291 """ 292 str: contract.func_name(type1,type2) 293 Return the function signature without the return values 294 """ 295 return "" 296 297 @property 298 def contains_assembly(self) -> bool: 299 return self._contains_assembly 300 301 @contains_assembly.setter 302 def contains_assembly(self, c: bool): 303 self._contains_assembly = c 304 305 def can_reenter(self, callstack: list[Union["Function", "Variable"]] | None = None) -> bool: 306 """ 307 Check if the function can re-enter 308 Follow internal calls. 309 Do not consider CREATE as potential re-enter, but check if the 310 destination's constructor can contain a call (recurs. follow nested CREATE) 311 For Solidity > 0.5, filter access to public variables and constant/pure/view 312 For call to this. check if the destination can re-enter 313 Do not consider Send/Transfer as there is not enough gas 314 :param callstack: used internally to check for recursion 315 :return bool: 316 """ 317 from slither.slithir.operations import Call 318 319 if self._can_reenter is None: 320 self._can_reenter = False 321 for ir in self.all_slithir_operations(): 322 if isinstance(ir, Call) and ir.can_reenter(callstack): 323 self._can_reenter = True 324 return True 325 return self._can_reenter 326 327 def can_send_eth(self) -> bool: 328 """ 329 Check if the function or any internal (not external) functions called by it can send eth 330 :return bool: 331 """ 332 from slither.slithir.operations import Call 333 334 if self._can_send_eth is None: 335 self._can_send_eth = False 336 for ir in self.all_slithir_operations(): 337 if isinstance(ir, Call) and ir.can_send_eth(): 338 self._can_send_eth = True 339 return True 340 return self._can_send_eth 341 342 @property 343 def is_checked(self) -> bool: 344 """ 345 Return true if the overflow are enabled by default 346 347 348 :return: 349 """ 350 351 return self.compilation_unit.solc_version >= "0.8.0" 352 353 @property 354 def id(self) -> str | None: 355 """ 356 Return the reference ID of the function, if available. 357 358 :return: 359 :rtype: 360 """ 361 return self._id 362 363 @id.setter 364 def id(self, new_id: str): 365 self._id = new_id 366 367 @property 368 @abstractmethod 369 def file_scope(self) -> "FileScope": 370 pass 371 372 # endregion 373 ################################################################################### 374 ################################################################################### 375 # region Type (FunctionType) 376 ################################################################################### 377 ################################################################################### 378 379 def set_function_type(self, t: FunctionType) -> None: 380 assert isinstance(t, FunctionType) 381 self._function_type = t 382 383 @property 384 def function_type(self) -> FunctionType | None: 385 return self._function_type 386 387 @function_type.setter 388 def function_type(self, t: FunctionType): 389 self._function_type = t 390 391 @property 392 def is_constructor(self) -> bool: 393 """ 394 bool: True if the function is the constructor 395 """ 396 return self._function_type == FunctionType.CONSTRUCTOR 397 398 @property 399 def is_constructor_variables(self) -> bool: 400 """ 401 bool: True if the function is the constructor of the variables 402 Slither has inbuilt functions to hold the state variables initialization 403 """ 404 return self._function_type in [ 405 FunctionType.CONSTRUCTOR_VARIABLES, 406 FunctionType.CONSTRUCTOR_CONSTANT_VARIABLES, 407 ] 408 409 @property 410 def is_fallback(self) -> bool: 411 """ 412 Determine if the function is the fallback function for the contract 413 Returns 414 (bool) 415 """ 416 return self._function_type == FunctionType.FALLBACK 417 418 @property 419 def is_receive(self) -> bool: 420 """ 421 Determine if the function is the receive function for the contract 422 Returns 423 (bool) 424 """ 425 return self._function_type == FunctionType.RECEIVE 426 427 # endregion 428 ################################################################################### 429 ################################################################################### 430 # region Payable 431 ################################################################################### 432 ################################################################################### 433 434 @property 435 def payable(self) -> bool: 436 """ 437 bool: True if the function is payable 438 """ 439 return self._payable 440 441 @payable.setter 442 def payable(self, p: bool): 443 self._payable = p 444 445 # endregion 446 ################################################################################### 447 ################################################################################### 448 # region Virtual 449 ################################################################################### 450 ################################################################################### 451 452 @property 453 def is_virtual(self) -> bool: 454 """ 455 Note for Solidity < 0.6.0 it will always be false 456 bool: True if the function is virtual 457 """ 458 return self._virtual 459 460 @is_virtual.setter 461 def is_virtual(self, v: bool): 462 self._virtual = v 463 464 @property 465 def is_override(self) -> bool: 466 """ 467 Note for Solidity < 0.6.0 it will always be false 468 bool: True if the function overrides a base function 469 """ 470 return len(self._overrides) > 0 471 472 @property 473 def overridden_by(self) -> list["FunctionContract"]: 474 """ 475 List["FunctionContract"]: List of functions in child contracts that override this function 476 This may include distinct instances of the same function due to inheritance 477 """ 478 return self._overridden_by 479 480 @property 481 def overrides(self) -> list["FunctionContract"]: 482 """ 483 List["FunctionContract"]: List of functions in parent contracts that this function overrides 484 This may include distinct instances of the same function due to inheritance 485 """ 486 return self._overrides 487 488 # endregion 489 ################################################################################### 490 ################################################################################### 491 # region Visibility 492 ################################################################################### 493 ################################################################################### 494 495 @property 496 def visibility(self) -> str: 497 """ 498 str: Function visibility 499 """ 500 assert self._visibility is not None 501 return self._visibility 502 503 @visibility.setter 504 def visibility(self, v: str): 505 self._visibility = v 506 507 def set_visibility(self, v: str) -> None: 508 self._visibility = v 509 510 @property 511 def view(self) -> bool: 512 """ 513 bool: True if the function is declared as view 514 """ 515 return self._view 516 517 @view.setter 518 def view(self, v: bool): 519 self._view = v 520 521 @property 522 def pure(self) -> bool: 523 """ 524 bool: True if the function is declared as pure 525 """ 526 return self._pure 527 528 @pure.setter 529 def pure(self, p: bool): 530 self._pure = p 531 532 @property 533 def is_shadowed(self) -> bool: 534 return self._is_shadowed 535 536 @is_shadowed.setter 537 def is_shadowed(self, is_shadowed): 538 self._is_shadowed = is_shadowed 539 540 @property 541 def shadows(self) -> bool: 542 return self._shadows 543 544 @shadows.setter 545 def shadows(self, _shadows: bool): 546 self._shadows = _shadows 547 548 # endregion 549 ################################################################################### 550 ################################################################################### 551 # region Function's body 552 ################################################################################### 553 ################################################################################### 554 555 @property 556 def is_implemented(self) -> bool: 557 """ 558 bool: True if the function is implemented 559 """ 560 return self._is_implemented 561 562 @is_implemented.setter 563 def is_implemented(self, is_impl: bool): 564 self._is_implemented = is_impl 565 566 @property 567 def is_empty(self) -> bool: 568 """ 569 bool: True if the function is empty, None if the function is an interface 570 """ 571 return self._is_empty 572 573 @is_empty.setter 574 def is_empty(self, empty: bool): 575 self._is_empty = empty 576 577 # endregion 578 ################################################################################### 579 ################################################################################### 580 # region Nodes 581 ################################################################################### 582 ################################################################################### 583 584 @property 585 def nodes(self) -> list["Node"]: 586 """ 587 list(Node): List of the nodes 588 """ 589 return list(self._nodes) 590 591 @nodes.setter 592 def nodes(self, nodes: list["Node"]): 593 self._nodes = nodes 594 595 @property 596 def entry_point(self) -> Optional["Node"]: 597 """ 598 Node: Entry point of the function 599 """ 600 return self._entry_point 601 602 @entry_point.setter 603 def entry_point(self, node: "Node"): 604 self._entry_point = node 605 606 def add_node(self, node: "Node") -> None: 607 if not self._entry_point: 608 self._entry_point = node 609 self._nodes.append(node) 610 611 @property 612 def nodes_ordered_dominators(self) -> list["Node"]: 613 # TODO: does not work properly; most likely due to modifier call 614 # This will not work for modifier call that lead to multiple nodes 615 # from slither.core.cfg.node import NodeType 616 if self._nodes_ordered_dominators is None: 617 self._nodes_ordered_dominators = [] 618 if self.entry_point: 619 self._compute_nodes_ordered_dominators(self.entry_point) 620 621 for node in self.nodes: 622 # if node.type == NodeType.OTHER_ENTRYPOINT: 623 if node not in self._nodes_ordered_dominators: 624 self._compute_nodes_ordered_dominators(node) 625 626 return self._nodes_ordered_dominators 627 628 def _compute_nodes_ordered_dominators(self, node: "Node"): 629 assert self._nodes_ordered_dominators is not None 630 if node in self._nodes_ordered_dominators: 631 return 632 self._nodes_ordered_dominators.append(node) 633 for dom in node.dominance_exploration_ordered: 634 self._compute_nodes_ordered_dominators(dom) 635 636 # endregion 637 ################################################################################### 638 ################################################################################### 639 # region Parameters 640 ################################################################################### 641 ################################################################################### 642 643 @property 644 def parameters(self) -> list["LocalVariable"]: 645 """ 646 list(LocalVariable): List of the parameters 647 """ 648 return list(self._parameters) 649 650 def add_parameters(self, p: "LocalVariable") -> None: 651 self._parameters.append(p) 652 653 @property 654 def parameters_ssa(self) -> list["LocalIRVariable"]: 655 """ 656 list(LocalIRVariable): List of the parameters (SSA form) 657 """ 658 return list(self._parameters_ssa) 659 660 def add_parameter_ssa(self, var: "LocalIRVariable") -> None: 661 self._parameters_ssa.append(var) 662 663 def parameters_src(self) -> SourceMapping: 664 return self._parameters_src 665 666 # endregion 667 ################################################################################### 668 ################################################################################### 669 # region Return values 670 ################################################################################### 671 ################################################################################### 672 673 @property 674 def return_type(self) -> list[Type] | None: 675 """ 676 Return the list of return type 677 If no return, return None 678 """ 679 returns = self.returns 680 if returns: 681 return [r.type for r in returns] 682 return None 683 684 def returns_src(self) -> SourceMapping: 685 return self._returns_src 686 687 @property 688 def type(self) -> list[Type] | None: 689 """ 690 Return the list of return type 691 If no return, return None 692 Alias of return_type 693 """ 694 return self.return_type 695 696 @property 697 def returns(self) -> list["LocalVariable"]: 698 """ 699 list(LocalVariable): List of the return variables 700 """ 701 return list(self._returns) 702 703 def add_return(self, r: "LocalVariable") -> None: 704 self._returns.append(r) 705 706 @property 707 def returns_ssa(self) -> list["LocalIRVariable"]: 708 """ 709 list(LocalIRVariable): List of the return variables (SSA form) 710 """ 711 return list(self._returns_ssa) 712 713 def add_return_ssa(self, var: "LocalIRVariable") -> None: 714 self._returns_ssa.append(var) 715 716 # endregion 717 ################################################################################### 718 ################################################################################### 719 # region Modifiers 720 ################################################################################### 721 ################################################################################### 722 723 @property 724 def modifiers(self) -> list[Union["Contract", "Function"]]: 725 """ 726 list(Modifier): List of the modifiers 727 Can be contract for constructor's calls 728 729 """ 730 return [c.modifier for c in self._modifiers] 731 732 def add_modifier(self, modif: "ModifierStatements") -> None: 733 self._modifiers.append(modif) 734 735 @property 736 def modifiers_statements(self) -> list[ModifierStatements]: 737 """ 738 list(ModifierCall): List of the modifiers call (include expression and irs) 739 """ 740 return list(self._modifiers) 741 742 @property 743 def explicit_base_constructor_calls(self) -> list["Function"]: 744 """ 745 list(Function): List of the base constructors called explicitly by this presumed constructor definition. 746 747 Base constructors implicitly or explicitly called by the contract definition will not be 748 included. 749 """ 750 # This is a list of contracts internally, so we convert it to a list of constructor functions. 751 return [ 752 c.modifier.constructors_declared 753 for c in self._explicit_base_constructor_calls 754 if c.modifier.constructors_declared 755 ] 756 757 @property 758 def explicit_base_constructor_calls_statements(self) -> list[ModifierStatements]: 759 """ 760 list(ModifierCall): List of the base constructors called explicitly by this presumed constructor definition. 761 762 """ 763 # This is a list of contracts internally, so we convert it to a list of constructor functions. 764 return list(self._explicit_base_constructor_calls) 765 766 def add_explicit_base_constructor_calls_statements(self, modif: ModifierStatements) -> None: 767 self._explicit_base_constructor_calls.append(modif) 768 769 # endregion 770 ################################################################################### 771 ################################################################################### 772 # region Variables 773 ################################################################################### 774 ################################################################################### 775 776 @property 777 def variables(self) -> list[LocalVariable]: 778 """ 779 Return all local variables 780 Include parameters and return values 781 """ 782 return list(self._variables.values()) 783 784 @property 785 def local_variables(self) -> list[LocalVariable]: 786 """ 787 Return all local variables (dont include parameters and return values) 788 """ 789 return list(set(self.variables) - set(self.returns) - set(self.parameters)) 790 791 @property 792 def variables_as_dict(self) -> dict[str, LocalVariable]: 793 return self._variables 794 795 @property 796 def variables_read(self) -> list["Variable"]: 797 """ 798 list(Variable): Variables read (local/state/solidity) 799 """ 800 return list(self._vars_read) 801 802 @property 803 def variables_written(self) -> list["Variable"]: 804 """ 805 list(Variable): Variables written (local/state/solidity) 806 """ 807 return list(self._vars_written) 808 809 @property 810 def state_variables_read(self) -> list["StateVariable"]: 811 """ 812 list(StateVariable): State variables read 813 """ 814 return list(self._state_vars_read) 815 816 @property 817 def solidity_variables_read(self) -> list["SolidityVariable"]: 818 """ 819 list(SolidityVariable): Solidity variables read 820 """ 821 return list(self._solidity_vars_read) 822 823 @property 824 def state_variables_written(self) -> list["StateVariable"]: 825 """ 826 list(StateVariable): State variables written 827 """ 828 return list(self._state_vars_written) 829 830 @property 831 def variables_read_or_written(self) -> list["Variable"]: 832 """ 833 list(Variable): Variables read or written (local/state/solidity) 834 """ 835 return list(self._vars_read_or_written) 836 837 @property 838 def variables_read_as_expression(self) -> list["Expression"]: 839 return self._expression_vars_read 840 841 @property 842 def variables_written_as_expression(self) -> list["Expression"]: 843 return self._expression_vars_written 844 845 @property 846 def slithir_variables(self) -> list["SlithIRVariable"]: 847 """ 848 Temporary and Reference Variables (not SSA form) 849 """ 850 851 return list(self._slithir_variables) 852 853 # endregion 854 ################################################################################### 855 ################################################################################### 856 # region Calls 857 ################################################################################### 858 ################################################################################### 859 860 @property 861 def internal_calls(self) -> list["InternalCall"]: 862 """ 863 list(InternalCall): List of IR operations for internal calls 864 """ 865 return list(self._internal_calls) 866 867 @property 868 def solidity_calls(self) -> list["SolidityCall"]: 869 """ 870 list(SolidityCall): List of IR operations for Solidity calls 871 """ 872 return list(self._solidity_calls) 873 874 @property 875 def high_level_calls(self) -> list[tuple["Contract", "HighLevelCall"]]: 876 """ 877 list(Tuple(Contract, "HighLevelCall")): List of call target contract and IR of the high level call 878 A variable is called in case of call to a public state variable 879 Include library calls 880 """ 881 return list(self._high_level_calls) 882 883 @property 884 def library_calls(self) -> list["LibraryCall"]: 885 """ 886 list(LibraryCall): List of IR operations for library calls 887 """ 888 return list(self._library_calls) 889 890 @property 891 def low_level_calls(self) -> list["LowLevelCall"]: 892 """ 893 list(LowLevelCall): List of IR operations for low level calls 894 A low level call is defined by 895 - the variable called 896 - the name of the function (call/delegatecall/callcode) 897 """ 898 return list(self._low_level_calls) 899 900 @property 901 def external_calls_as_expressions(self) -> list["Expression"]: 902 """ 903 list(ExpressionCall): List of message calls (that creates a transaction) 904 """ 905 return list(self._external_calls_as_expressions) 906 907 # endregion 908 ################################################################################### 909 ################################################################################### 910 # region Expressions 911 ################################################################################### 912 ################################################################################### 913 914 @property 915 def calls_as_expressions(self) -> list["Expression"]: 916 return self._expression_calls 917 918 @property 919 def expressions(self) -> list["Expression"]: 920 """ 921 list(Expression): List of the expressions 922 """ 923 if self._expressions is None: 924 expressionss = [n.expression for n in self.nodes] 925 expressions = [e for e in expressionss if e] 926 self._expressions = expressions 927 return self._expressions 928 929 @property 930 def return_values(self) -> list["SlithIRVariable"]: 931 """ 932 list(Return Values): List of the return values 933 """ 934 from slither.core.cfg.node import NodeType 935 from slither.slithir.operations import Return 936 from slither.slithir.variables import Constant 937 938 if self._return_values is None: 939 return_values = [] 940 returns = [n for n in self.nodes if n.type == NodeType.RETURN] 941 [ 942 return_values.extend(ir.values) 943 for node in returns 944 for ir in node.irs 945 if isinstance(ir, Return) 946 ] 947 self._return_values = list({x for x in return_values if not isinstance(x, Constant)}) 948 return self._return_values 949 950 @property 951 def return_values_ssa(self) -> list["SlithIRVariable"]: 952 """ 953 list(Return Values in SSA form): List of the return values in ssa form 954 """ 955 from slither.core.cfg.node import NodeType 956 from slither.slithir.operations import Return 957 from slither.slithir.variables import Constant 958 959 if self._return_values_ssa is None: 960 return_values_ssa = [] 961 returns = [n for n in self.nodes if n.type == NodeType.RETURN] 962 [ 963 return_values_ssa.extend(ir.values) 964 for node in returns 965 for ir in node.irs_ssa 966 if isinstance(ir, Return) 967 ] 968 self._return_values_ssa = list( 969 {x for x in return_values_ssa if not isinstance(x, Constant)} 970 ) 971 return self._return_values_ssa 972 973 # endregion 974 ################################################################################### 975 ################################################################################### 976 # region SlithIR 977 ################################################################################### 978 ################################################################################### 979 980 @property 981 def slithir_operations(self) -> list["Operation"]: 982 """ 983 list(Operation): List of the slithir operations 984 """ 985 if self._slithir_operations is None: 986 operationss = [n.irs for n in self.nodes] 987 operations = [item for sublist in operationss for item in sublist if item] 988 self._slithir_operations = operations 989 return self._slithir_operations 990 991 @property 992 def slithir_ssa_operations(self) -> list["Operation"]: 993 """ 994 list(Operation): List of the slithir operations (SSA) 995 """ 996 if self._slithir_ssa_operations is None: 997 operationss = [n.irs_ssa for n in self.nodes] 998 operations = [item for sublist in operationss for item in sublist if item] 999 self._slithir_ssa_operations = operations 1000 return self._slithir_ssa_operations 1001 1002 # endregion 1003 ################################################################################### 1004 ################################################################################### 1005 # region Signature 1006 ################################################################################### 1007 ################################################################################### 1008 1009 @property 1010 def solidity_signature(self) -> str: 1011 """ 1012 Return a signature following the Solidity Standard 1013 Contract and converted into address 1014 1015 It might still keep internal types (ex: structure name) for internal functions. 1016 The reason is that internal functions allows recursive structure definition, which 1017 can't be converted following the Solidity stand ard 1018 1019 :return: the solidity signature 1020 """ 1021 if self._solidity_signature is None: 1022 parameters = [ 1023 convert_type_for_solidity_signature_to_string(x.type) for x in self.parameters 1024 ] 1025 self._solidity_signature = self.name + "(" + ",".join(parameters) + ")" 1026 return self._solidity_signature 1027 1028 @property 1029 def signature(self) -> tuple[str, list[str], list[str]]: 1030 """ 1031 (str, list(str), list(str)): Function signature as 1032 (name, list parameters type, list return values type) 1033 """ 1034 # FIXME memoizing this function is not working properly for vyper 1035 # if self._signature is None: 1036 return ( 1037 self.name, 1038 [str(x.type) for x in self.parameters], 1039 [str(x.type) for x in self.returns], 1040 ) 1041 # self._signature = signature 1042 # return self._signature 1043 1044 @property 1045 def signature_str(self) -> str: 1046 """ 1047 str: func_name(type1,type2) returns (type3) 1048 Return the function signature as a str (contains the return values) 1049 """ 1050 if self._signature_str is None: 1051 name, parameters, returnVars = self.signature 1052 self._signature_str = ( 1053 name + "(" + ",".join(parameters) + ") returns(" + ",".join(returnVars) + ")" 1054 ) 1055 return self._signature_str 1056 1057 # endregion 1058 ################################################################################### 1059 ################################################################################### 1060 # region Functions 1061 ################################################################################### 1062 ################################################################################### 1063 1064 @property 1065 @abstractmethod 1066 def functions_shadowed(self) -> list["Function"]: 1067 pass 1068 1069 # endregion 1070 ################################################################################### 1071 ################################################################################### 1072 # region Reachable 1073 ################################################################################### 1074 ################################################################################### 1075 1076 @property 1077 def reachable_from_nodes(self) -> set[ReacheableNode]: 1078 """ 1079 Return 1080 ReacheableNode 1081 """ 1082 return self._reachable_from_nodes 1083 1084 @property 1085 def reachable_from_functions(self) -> set["Function"]: 1086 return self._reachable_from_functions 1087 1088 @property 1089 def all_reachable_from_functions(self) -> set["Function"]: 1090 """ 1091 Give the recursive version of reachable_from_functions (all the functions that lead to call self in the CFG) 1092 """ 1093 if self._all_reachable_from_functions is None: 1094 functions: set[Function] = set() 1095 1096 new_functions = self.reachable_from_functions 1097 # iterate until we have are finding new functions 1098 while new_functions and not new_functions.issubset(functions): 1099 functions = functions.union(new_functions) 1100 # Use a temporary set, because we iterate over new_functions 1101 new_functionss: set[Function] = set() 1102 for f in new_functions: 1103 new_functionss = new_functionss.union(f.reachable_from_functions) 1104 new_functions = new_functionss - functions 1105 1106 self._all_reachable_from_functions = functions 1107 return self._all_reachable_from_functions 1108 1109 def add_reachable_from_node(self, n: "Node", ir: "Operation") -> None: 1110 self._reachable_from_nodes.add(ReacheableNode(n, ir)) 1111 self._reachable_from_functions.add(n.function) 1112 1113 # endregion 1114 ################################################################################### 1115 ################################################################################### 1116 # region Recursive getters 1117 ################################################################################### 1118 ################################################################################### 1119 1120 def _explore_functions(self, f_new_values: Callable[["Function"], list]) -> list[Any]: 1121 values = f_new_values(self) 1122 explored: set[Function] = {self} 1123 to_explore_set: set[Function] = set() 1124 1125 for ir in self.internal_calls: 1126 if isinstance(ir.function, Function) and ir.function not in explored: 1127 to_explore_set.add(ir.function) 1128 for ir in self.library_calls: 1129 if isinstance(ir.function, Function) and ir.function not in explored: 1130 to_explore_set.add(ir.function) 1131 for m in self.modifiers: 1132 if m not in explored: 1133 to_explore_set.add(m) 1134 1135 while to_explore_set: 1136 f = to_explore_set.pop() 1137 if f in explored: 1138 continue 1139 explored.add(f) 1140 1141 values += f_new_values(f) 1142 1143 for ir in f.internal_calls: 1144 if isinstance(ir.function, Function) and ir.function not in explored: 1145 to_explore_set.add(ir.function) 1146 for ir in f.library_calls: 1147 if isinstance(ir.function, Function) and ir.function not in explored: 1148 to_explore_set.add(ir.function) 1149 for m in f.modifiers: 1150 if m not in explored: 1151 to_explore_set.add(m) 1152 1153 return list(set(values)) 1154 1155 def all_variables_read(self) -> list["Variable"]: 1156 """recursive version of variables_read""" 1157 if self._all_variables_read is None: 1158 self._all_variables_read = self._explore_functions(lambda x: x.variables_read) 1159 return self._all_variables_read 1160 1161 def all_variables_written(self) -> list["Variable"]: 1162 """recursive version of variables_written""" 1163 if self._all_variables_written is None: 1164 self._all_variables_written = self._explore_functions(lambda x: x.variables_written) 1165 return self._all_variables_written 1166 1167 def all_state_variables_read(self) -> list["StateVariable"]: 1168 """recursive version of variables_read""" 1169 if self._all_state_variables_read is None: 1170 self._all_state_variables_read = self._explore_functions( 1171 lambda x: x.state_variables_read 1172 ) 1173 return self._all_state_variables_read 1174 1175 def all_solidity_variables_read(self) -> list[SolidityVariable]: 1176 """recursive version of solidity_read""" 1177 if self._all_solidity_variables_read is None: 1178 self._all_solidity_variables_read = self._explore_functions( 1179 lambda x: x.solidity_variables_read 1180 ) 1181 return self._all_solidity_variables_read 1182 1183 def all_slithir_variables(self) -> list["SlithIRVariable"]: 1184 """recursive version of slithir_variables""" 1185 if self._all_slithir_variables is None: 1186 self._all_slithir_variables = self._explore_functions(lambda x: x.slithir_variables) 1187 return self._all_slithir_variables 1188 1189 def all_nodes(self) -> list["Node"]: 1190 """recursive version of nodes""" 1191 if self._all_nodes is None: 1192 self._all_nodes = self._explore_functions(lambda x: x.nodes) 1193 return self._all_nodes 1194 1195 def all_expressions(self) -> list["Expression"]: 1196 """recursive version of variables_read""" 1197 if self._all_expressions is None: 1198 self._all_expressions = self._explore_functions(lambda x: x.expressions) 1199 return self._all_expressions 1200 1201 def all_slithir_operations(self) -> list["Operation"]: 1202 if self._all_slithir_operations is None: 1203 self._all_slithir_operations = self._explore_functions(lambda x: x.slithir_operations) 1204 return self._all_slithir_operations 1205 1206 def all_state_variables_written(self) -> list[StateVariable]: 1207 """recursive version of variables_written""" 1208 if self._all_state_variables_written is None: 1209 self._all_state_variables_written = self._explore_functions( 1210 lambda x: x.state_variables_written 1211 ) 1212 return self._all_state_variables_written 1213 1214 def all_internal_calls(self) -> list["InternalCall"]: 1215 """recursive version of internal_calls""" 1216 if self._all_internals_calls is None: 1217 self._all_internals_calls = self._explore_functions(lambda x: x.internal_calls) 1218 return self._all_internals_calls 1219 1220 def all_low_level_calls(self) -> list["LowLevelCall"]: 1221 """recursive version of low_level calls""" 1222 if self._all_low_level_calls is None: 1223 self._all_low_level_calls = self._explore_functions(lambda x: x.low_level_calls) 1224 return self._all_low_level_calls 1225 1226 def all_high_level_calls(self) -> list[tuple["Contract", "HighLevelCall"]]: 1227 """recursive version of high_level calls""" 1228 if self._all_high_level_calls is None: 1229 self._all_high_level_calls = self._explore_functions(lambda x: x.high_level_calls) 1230 return self._all_high_level_calls 1231 1232 def all_library_calls(self) -> list["LibraryCall"]: 1233 """recursive version of library calls""" 1234 if self._all_library_calls is None: 1235 self._all_library_calls = self._explore_functions(lambda x: x.library_calls) 1236 return self._all_library_calls 1237 1238 def all_solidity_calls(self) -> list["SolidityCall"]: 1239 """recursive version of solidity calls""" 1240 if self._all_solidity_calls is None: 1241 self._all_solidity_calls = self._explore_functions(lambda x: x.solidity_calls) 1242 return self._all_solidity_calls 1243 1244 @staticmethod 1245 def _explore_func_cond_read(func: "Function", include_loop: bool) -> list["StateVariable"]: 1246 ret = [n.state_variables_read for n in func.nodes if n.is_conditional(include_loop)] 1247 return [item for sublist in ret for item in sublist] 1248 1249 def all_conditional_state_variables_read(self, include_loop=True) -> list["StateVariable"]: 1250 """ 1251 Return the state variable used in a condition 1252 1253 Over approximate and also return index access 1254 It won't work if the variable is assigned to a temp variable 1255 """ 1256 if include_loop: 1257 if self._all_conditional_state_variables_read_with_loop is None: 1258 self._all_conditional_state_variables_read_with_loop = self._explore_functions( 1259 lambda x: self._explore_func_cond_read(x, include_loop) 1260 ) 1261 return self._all_conditional_state_variables_read_with_loop 1262 if self._all_conditional_state_variables_read is None: 1263 self._all_conditional_state_variables_read = self._explore_functions( 1264 lambda x: self._explore_func_cond_read(x, include_loop) 1265 ) 1266 return self._all_conditional_state_variables_read 1267 1268 @staticmethod 1269 def _solidity_variable_in_binary(node: "Node") -> list[SolidityVariable]: 1270 from slither.slithir.operations.binary import Binary 1271 1272 ret = [] 1273 for ir in node.irs: 1274 if isinstance(ir, Binary): 1275 ret += ir.read 1276 return [var for var in ret if isinstance(var, SolidityVariable)] 1277 1278 @staticmethod 1279 def _explore_func_conditional( 1280 func: "Function", 1281 f: Callable[["Node"], list[SolidityVariable]], 1282 include_loop: bool, 1283 ) -> list[Any]: 1284 ret = [f(n) for n in func.nodes if n.is_conditional(include_loop)] 1285 return [item for sublist in ret for item in sublist] 1286 1287 def all_conditional_solidity_variables_read( 1288 self, include_loop: bool = True 1289 ) -> list[SolidityVariable]: 1290 """ 1291 Return the Solidity variables directly used in a condition 1292 1293 Use of the IR to filter index access 1294 Assumption: the solidity vars are used directly in the conditional node 1295 It won't work if the variable is assigned to a temp variable 1296 """ 1297 if include_loop: 1298 if self._all_conditional_solidity_variables_read_with_loop is None: 1299 self._all_conditional_solidity_variables_read_with_loop = self._explore_functions( 1300 lambda x: self._explore_func_conditional( 1301 x, self._solidity_variable_in_binary, include_loop 1302 ) 1303 ) 1304 return self._all_conditional_solidity_variables_read_with_loop 1305 1306 if self._all_conditional_solidity_variables_read is None: 1307 self._all_conditional_solidity_variables_read = self._explore_functions( 1308 lambda x: self._explore_func_conditional( 1309 x, self._solidity_variable_in_binary, include_loop 1310 ) 1311 ) 1312 return self._all_conditional_solidity_variables_read 1313 1314 @staticmethod 1315 def _solidity_variable_in_internal_calls(node: "Node") -> list[SolidityVariable]: 1316 from slither.slithir.operations.internal_call import InternalCall 1317 1318 ret = [] 1319 for ir in node.irs: 1320 if isinstance(ir, InternalCall): 1321 ret += ir.read 1322 return [var for var in ret if isinstance(var, SolidityVariable)] 1323 1324 @staticmethod 1325 def _explore_func_nodes( 1326 func: "Function", f: Callable[["Node"], list[SolidityVariable]] 1327 ) -> list[Any | SolidityVariableComposed]: 1328 ret = [f(n) for n in func.nodes] 1329 return [item for sublist in ret for item in sublist] 1330 1331 def all_solidity_variables_used_as_args(self) -> list[SolidityVariable]: 1332 """ 1333 Return the Solidity variables directly used in a call 1334 1335 Use of the IR to filter index access 1336 Used to catch check(msg.sender) 1337 """ 1338 if self._all_solidity_variables_used_as_args is None: 1339 self._all_solidity_variables_used_as_args = self._explore_functions( 1340 lambda x: self._explore_func_nodes(x, self._solidity_variable_in_internal_calls) 1341 ) 1342 return self._all_solidity_variables_used_as_args 1343 1344 # endregion 1345 ################################################################################### 1346 ################################################################################### 1347 # region Visitor 1348 ################################################################################### 1349 ################################################################################### 1350 1351 def apply_visitor(self, Visitor: Callable) -> list: 1352 """ 1353 Apply a visitor to all the function expressions 1354 Args: 1355 Visitor: slither.visitors 1356 Returns 1357 list(): results of the visit 1358 """ 1359 expressions = self.expressions 1360 v = [Visitor(e).result() for e in expressions] 1361 return [item for sublist in v for item in sublist] 1362 1363 # endregion 1364 ################################################################################### 1365 ################################################################################### 1366 # region Getters from/to object 1367 ################################################################################### 1368 ################################################################################### 1369 1370 def get_local_variable_from_name(self, variable_name: str) -> LocalVariable | None: 1371 """ 1372 Return a local variable from a name 1373 1374 Args: 1375 variable_name (str): name of the variable 1376 Returns: 1377 LocalVariable 1378 """ 1379 return next((v for v in self.variables if v.name == variable_name), None) 1380 1381 # endregion 1382 ################################################################################### 1383 ################################################################################### 1384 # region Export 1385 ################################################################################### 1386 ################################################################################### 1387 1388 def cfg_to_dot(self, filename: str): 1389 """ 1390 Export the function to a dot file 1391 Args: 1392 filename (str) 1393 """ 1394 with open(filename, "w", encoding="utf8") as f: 1395 f.write("digraph{\n") 1396 for node in self.nodes: 1397 f.write(f'{node.node_id}[label="{node!s}"];\n') 1398 for son in node.sons: 1399 f.write(f"{node.node_id}->{son.node_id};\n") 1400 1401 f.write("}\n") 1402 1403 def dominator_tree_to_dot(self, filename: str): 1404 """ 1405 Export the dominator tree of the function to a dot file 1406 Args: 1407 filename (str) 1408 """ 1409 1410 def description(node): 1411 desc = f"{node}\n" 1412 desc += f"id: {node.node_id}" 1413 if node.dominance_frontier: 1414 desc += f"\ndominance frontier: {[n.node_id for n in node.dominance_frontier]}" 1415 return desc 1416 1417 with open(filename, "w", encoding="utf8") as f: 1418 f.write("digraph{\n") 1419 for node in self.nodes: 1420 f.write(f'{node.node_id}[label="{description(node)}"];\n') 1421 if node.immediate_dominator: 1422 f.write(f"{node.immediate_dominator.node_id}->{node.node_id};\n") 1423 1424 f.write("}\n") 1425 1426 def slithir_cfg_to_dot(self, filename: str): 1427 """ 1428 Export the CFG to a DOT file. The nodes includes the Solidity expressions and the IRs 1429 :param filename: 1430 :return: 1431 """ 1432 content = self.slithir_cfg_to_dot_str() 1433 with open(filename, "w", encoding="utf8") as f: 1434 f.write(content) 1435 1436 def slithir_cfg_to_dot_str(self, skip_expressions: bool = False) -> str: 1437 """ 1438 Export the CFG to a DOT format. The nodes includes the Solidity expressions and the IRs 1439 :return: the DOT content 1440 :rtype: str 1441 """ 1442 from slither.core.cfg.node import NodeType 1443 1444 content = "" 1445 content += "digraph{\n" 1446 for node in self.nodes: 1447 label = f"Node Type: {node.type.value} {node.node_id}\n" 1448 if node.expression and not skip_expressions: 1449 label += f"\nEXPRESSION:\n{node.expression}\n" 1450 if node.irs and not skip_expressions: 1451 label += "\nIRs:\n" + "\n".join([str(ir) for ir in node.irs]) 1452 content += f'{node.node_id}[label="{label}"];\n' 1453 if node.type in [NodeType.IF, NodeType.IFLOOP]: 1454 true_node = node.son_true 1455 if true_node: 1456 content += f'{node.node_id}->{true_node.node_id}[label="True"];\n' 1457 false_node = node.son_false 1458 if false_node: 1459 content += f'{node.node_id}->{false_node.node_id}[label="False"];\n' 1460 else: 1461 for son in node.sons: 1462 content += f"{node.node_id}->{son.node_id};\n" 1463 1464 content += "}\n" 1465 return content 1466 1467 # endregion 1468 ################################################################################### 1469 ################################################################################### 1470 # region Summary information 1471 ################################################################################### 1472 ################################################################################### 1473 1474 def is_reading(self, variable: "Variable") -> bool: 1475 """ 1476 Check if the function reads the variable 1477 Args: 1478 variable (Variable): 1479 Returns: 1480 bool: True if the variable is read 1481 """ 1482 return variable in self.variables_read 1483 1484 def is_reading_in_conditional_node(self, variable: "Variable") -> bool: 1485 """ 1486 Check if the function reads the variable in a IF node 1487 Args: 1488 variable (Variable): 1489 Returns: 1490 bool: True if the variable is read 1491 """ 1492 variables_reads = [n.variables_read for n in self.nodes if n.contains_if()] 1493 variables_read = [item for sublist in variables_reads for item in sublist] 1494 return variable in variables_read 1495 1496 def is_reading_in_require_or_assert(self, variable: "Variable") -> bool: 1497 """ 1498 Check if the function reads the variable in an require or assert 1499 Args: 1500 variable (Variable): 1501 Returns: 1502 bool: True if the variable is read 1503 """ 1504 variables_reads = [n.variables_read for n in self.nodes if n.contains_require_or_assert()] 1505 variables_read = [item for sublist in variables_reads for item in sublist] 1506 return variable in variables_read 1507 1508 def is_writing(self, variable: "Variable") -> bool: 1509 """ 1510 Check if the function writes the variable 1511 Args: 1512 variable (Variable): 1513 Returns: 1514 bool: True if the variable is written 1515 """ 1516 return variable in self.variables_written 1517 1518 @abstractmethod 1519 def get_summary( 1520 self, 1521 ) -> tuple[str, str, str, list[str], list[str], list[str], list[str], list[str]]: 1522 pass 1523 1524 def is_protected(self) -> bool: 1525 """ 1526 Determine if the function is protected using a check on msg.sender 1527 1528 Consider onlyOwner as a safe modifier. 1529 If the owner functionality is incorrectly implemented, this will lead to incorrectly 1530 classify the function as protected 1531 1532 Otherwise only detects if msg.sender is directly used in a condition 1533 For example, it wont work for: 1534 address a = msg.sender 1535 require(a == owner) 1536 Returns 1537 (bool) 1538 """ 1539 1540 if self._is_protected is None: 1541 if self.is_constructor: 1542 self._is_protected = True 1543 return True 1544 if "onlyOwner" in [m.name for m in self.modifiers]: 1545 self._is_protected = True 1546 return True 1547 conditional_vars = self.all_conditional_solidity_variables_read(include_loop=False) 1548 args_vars = self.all_solidity_variables_used_as_args() 1549 self._is_protected = ( 1550 SolidityVariableComposed("msg.sender") in conditional_vars + args_vars 1551 ) 1552 return self._is_protected 1553 1554 @property 1555 def is_reentrant(self) -> bool: 1556 """ 1557 Determine if the function can be re-entered 1558 """ 1559 reentrancy_modifier = "nonReentrant" 1560 1561 if self.function_language == FunctionLanguage.Vyper: 1562 reentrancy_modifier = "nonreentrant(lock)" 1563 1564 # TODO: compare with hash of known nonReentrant modifier instead of the name 1565 if reentrancy_modifier in [m.name for m in self.modifiers]: 1566 return False 1567 1568 if self.visibility in ["public", "external"]: 1569 return True 1570 1571 # If it's an internal function, check if all its entry points have the nonReentrant modifier 1572 all_entry_points = [ 1573 f for f in self.all_reachable_from_functions if f.visibility in ["public", "external"] 1574 ] 1575 if not all_entry_points: 1576 return True 1577 return not all( 1578 reentrancy_modifier in [m.name for m in f.modifiers] for f in all_entry_points 1579 ) 1580 1581 # endregion 1582 ################################################################################### 1583 ################################################################################### 1584 # region Analyses 1585 ################################################################################### 1586 ################################################################################### 1587 1588 def _analyze_read_write(self) -> None: 1589 """Compute variables read/written/...""" 1590 write_var = [x.variables_written_as_expression for x in self.nodes] 1591 write_var = [x for x in write_var if x] 1592 write_var = [item for sublist in write_var for item in sublist] 1593 write_var = list(set(write_var)) 1594 # Remove duplicate if they share the same string representation 1595 write_var = [ 1596 next(obj) 1597 for i, obj in groupby(sorted(write_var, key=lambda x: str(x)), lambda x: str(x)) 1598 ] 1599 self._expression_vars_written = write_var 1600 1601 write_var = [x.variables_written for x in self.nodes] 1602 write_var = [x for x in write_var if x] 1603 write_var = [item for sublist in write_var for item in sublist] 1604 write_var = list(set(write_var)) 1605 # Remove duplicate if they share the same string representation 1606 write_var = [ 1607 next(obj) 1608 for i, obj in groupby(sorted(write_var, key=lambda x: str(x)), lambda x: str(x)) 1609 ] 1610 self._vars_written = write_var 1611 1612 read_var = [x.variables_read_as_expression for x in self.nodes] 1613 read_var = [x for x in read_var if x] 1614 read_var = [item for sublist in read_var for item in sublist] 1615 # Remove duplicate if they share the same string representation 1616 read_var = [ 1617 next(obj) 1618 for i, obj in groupby(sorted(read_var, key=lambda x: str(x)), lambda x: str(x)) 1619 ] 1620 self._expression_vars_read = read_var 1621 1622 read_var = [x.variables_read for x in self.nodes] 1623 read_var = [x for x in read_var if x] 1624 read_var = [item for sublist in read_var for item in sublist] 1625 # Remove duplicate if they share the same string representation 1626 read_var = [ 1627 next(obj) 1628 for i, obj in groupby(sorted(read_var, key=lambda x: str(x)), lambda x: str(x)) 1629 ] 1630 self._vars_read = read_var 1631 1632 self._state_vars_written = [ 1633 x for x in self.variables_written if isinstance(x, StateVariable) 1634 ] 1635 self._state_vars_read = [x for x in self.variables_read if isinstance(x, StateVariable)] 1636 self._solidity_vars_read = [ 1637 x for x in self.variables_read if isinstance(x, SolidityVariable) 1638 ] 1639 1640 self._vars_read_or_written = self._vars_written + self._vars_read 1641 1642 slithir_variables = [x.slithir_variables for x in self.nodes] 1643 slithir_variables = [x for x in slithir_variables if x] 1644 self._slithir_variables = [item for sublist in slithir_variables for item in sublist] 1645 1646 def _analyze_calls(self) -> None: 1647 calls = [x.calls_as_expression for x in self.nodes] 1648 calls = [x for x in calls if x] 1649 calls = [item for sublist in calls for item in sublist] 1650 self._expression_calls = list(set(calls)) 1651 1652 internal_calls = [x.internal_calls for x in self.nodes] 1653 internal_calls = [x for x in internal_calls if x] 1654 internal_calls = [item for sublist in internal_calls for item in sublist] 1655 self._internal_calls = list(set(internal_calls)) 1656 1657 self._solidity_calls = [ 1658 ir for ir in internal_calls if isinstance(ir.function, SolidityFunction) 1659 ] 1660 1661 low_level_calls = [x.low_level_calls for x in self.nodes] 1662 low_level_calls = [x for x in low_level_calls if x] 1663 low_level_calls = [item for sublist in low_level_calls for item in sublist] 1664 self._low_level_calls = list(set(low_level_calls)) 1665 1666 high_level_calls = [x.high_level_calls for x in self.nodes] 1667 high_level_calls = [x for x in high_level_calls if x] 1668 high_level_calls = [item for sublist in high_level_calls for item in sublist] 1669 self._high_level_calls = list(set(high_level_calls)) 1670 1671 library_calls = [x.library_calls for x in self.nodes] 1672 library_calls = [x for x in library_calls if x] 1673 library_calls = [item for sublist in library_calls for item in sublist] 1674 self._library_calls = list(set(library_calls)) 1675 1676 external_calls_as_expressions = [x.external_calls_as_expressions for x in self.nodes] 1677 external_calls_as_expressions = [x for x in external_calls_as_expressions if x] 1678 external_calls_as_expressions = [ 1679 item for sublist in external_calls_as_expressions for item in sublist 1680 ] 1681 self._external_calls_as_expressions = list(set(external_calls_as_expressions)) 1682 1683 # endregion 1684 ################################################################################### 1685 ################################################################################### 1686 # region Nodes 1687 ################################################################################### 1688 ################################################################################### 1689 1690 def new_node( 1691 self, node_type: "NodeType", src: str | dict, scope: Union[Scope, "Function"] 1692 ) -> "Node": 1693 from slither.core.cfg.node import Node 1694 1695 node = Node(node_type, self._counter_nodes, scope, self.file_scope) 1696 node.set_offset(src, self.compilation_unit) 1697 self._counter_nodes += 1 1698 node.set_function(self) 1699 self._nodes.append(node) 1700 1701 return node 1702 1703 # endregion 1704 ################################################################################### 1705 ################################################################################### 1706 # region SlithIr and SSA 1707 ################################################################################### 1708 ################################################################################### 1709 1710 def _get_last_ssa_variable_instances( 1711 self, target_state: bool, target_local: bool 1712 ) -> dict[str, set["SlithIRVariable"]]: 1713 from slither.slithir.variables import ReferenceVariable 1714 from slither.slithir.operations import OperationWithLValue 1715 from slither.core.cfg.node import NodeType 1716 1717 if not self.is_implemented: 1718 return {} 1719 1720 if self._entry_point is None: 1721 return {} 1722 # node, values 1723 to_explore: list[tuple[Node, dict]] = [(self._entry_point, {})] 1724 # node -> values 1725 explored: dict = {} 1726 # name -> instances 1727 ret: dict = {} 1728 1729 while to_explore: 1730 node, values = to_explore[0] 1731 to_explore = to_explore[1::] 1732 1733 if node.type != NodeType.ENTRYPOINT: 1734 for ir_ssa in node.irs_ssa: 1735 if isinstance(ir_ssa, OperationWithLValue): 1736 lvalue = ir_ssa.lvalue 1737 if isinstance(lvalue, ReferenceVariable): 1738 lvalue = lvalue.points_to_origin 1739 if isinstance(lvalue, StateVariable) and target_state: 1740 values[lvalue.canonical_name] = {lvalue} 1741 if isinstance(lvalue, LocalVariable) and target_local: 1742 values[lvalue.canonical_name] = {lvalue} 1743 1744 # Check for fixpoint 1745 if node in explored: 1746 if values == explored[node]: 1747 continue 1748 for k, instances in values.items(): 1749 if k not in explored[node]: 1750 explored[node][k] = set() 1751 explored[node][k] |= instances 1752 values = explored[node] 1753 else: 1754 explored[node] = values 1755 1756 # Return condition 1757 if node.will_return: 1758 for name, instances in values.items(): 1759 if name not in ret: 1760 ret[name] = set() 1761 ret[name] |= instances 1762 1763 for son in node.sons: 1764 to_explore.append((son, dict(values))) 1765 1766 return ret 1767 1768 def get_last_ssa_state_variables_instances( 1769 self, 1770 ) -> dict[str, set["SlithIRVariable"]]: 1771 return self._get_last_ssa_variable_instances(target_state=True, target_local=False) 1772 1773 def get_last_ssa_local_variables_instances( 1774 self, 1775 ) -> dict[str, set["SlithIRVariable"]]: 1776 return self._get_last_ssa_variable_instances(target_state=False, target_local=True) 1777 1778 @staticmethod 1779 def _unchange_phi(ir: "Operation") -> bool: 1780 from slither.slithir.operations import Phi, PhiCallback 1781 1782 if not isinstance(ir, (Phi, PhiCallback)) or len(ir.rvalues) > 1: 1783 return False 1784 if not ir.rvalues: 1785 return True 1786 return ir.rvalues[0] == ir.lvalue 1787 1788 def _fix_phi_entry( 1789 self, 1790 node: "Node", 1791 last_state_variables_instances: dict[str, list["StateVariable"]], 1792 initial_state_variables_instances: dict[str, "StateVariable"], 1793 ) -> None: 1794 from slither.slithir.variables import Constant, StateIRVariable, LocalIRVariable 1795 1796 for ir in node.irs_ssa: 1797 if isinstance(ir.lvalue, StateIRVariable): 1798 additional = [initial_state_variables_instances[ir.lvalue.canonical_name]] 1799 additional += last_state_variables_instances[ir.lvalue.canonical_name] 1800 ir.rvalues = list(set(additional + ir.rvalues)) 1801 # function parameter that are storage pointer 1802 else: 1803 # find index of the parameter 1804 idx = self.parameters.index(ir.lvalue.non_ssa_version) 1805 # find non ssa version of that index 1806 additional = [n.ir.arguments[idx] for n in self.reachable_from_nodes] 1807 additional = unroll(additional) 1808 additional = [a for a in additional if not isinstance(a, Constant)] 1809 ir.rvalues = list(set(additional + ir.rvalues)) 1810 1811 if isinstance(ir.lvalue, LocalIRVariable) and ir.lvalue.is_storage: 1812 # Update the refers_to to point to the phi rvalues 1813 # This basically means that the local variable is a storage that point to any 1814 # state variable that the storage pointer alias analysis found 1815 ir.lvalue.refers_to = [ 1816 rvalue for rvalue in ir.rvalues if isinstance(rvalue, StateIRVariable) 1817 ] 1818 1819 def fix_phi( 1820 self, 1821 last_state_variables_instances: dict[str, list["StateVariable"]], 1822 initial_state_variables_instances: dict[str, "StateVariable"], 1823 ) -> None: 1824 from slither.slithir.operations import InternalCall, PhiCallback, Phi 1825 from slither.slithir.variables import StateIRVariable, LocalIRVariable 1826 1827 for node in self.nodes: 1828 if node == self.entry_point: 1829 self._fix_phi_entry( 1830 node, last_state_variables_instances, initial_state_variables_instances 1831 ) 1832 for ir in node.irs_ssa: 1833 if isinstance(ir, PhiCallback): 1834 callee_ir = ir.callee_ir 1835 if isinstance(callee_ir, InternalCall): 1836 last_ssa = callee_ir.function.get_last_ssa_state_variables_instances() 1837 if ir.lvalue.canonical_name in last_ssa: 1838 ir.rvalues = list(last_ssa[ir.lvalue.canonical_name]) 1839 else: 1840 ir.rvalues = [ir.lvalue] 1841 else: 1842 additional = last_state_variables_instances[ir.lvalue.canonical_name] 1843 ir.rvalues = list(set(additional + ir.rvalues)) 1844 1845 # Propage storage ref information if it does not exist 1846 # This can happen if the refers_to variable was discovered through the phi operator on function parameter 1847 # aka you have storage pointer as function parameter 1848 # instead of having a storage pointer for which the aliases belong to the function body 1849 if ( 1850 isinstance(ir, Phi) 1851 and isinstance(ir.lvalue, LocalIRVariable) 1852 and ir.lvalue.is_storage 1853 and not ir.lvalue.refers_to 1854 ): 1855 refers_to = [] 1856 for candidate in ir.rvalues: 1857 if isinstance(candidate, StateIRVariable): 1858 refers_to.append(candidate) 1859 if isinstance(candidate, LocalIRVariable) and candidate.is_storage: 1860 refers_to += candidate.refers_to 1861 1862 ir.lvalue.refers_to = refers_to 1863 1864 node.irs_ssa = [ir for ir in node.irs_ssa if not self._unchange_phi(ir)] 1865 1866 def generate_slithir_and_analyze(self) -> None: 1867 for node in self.nodes: 1868 node.slithir_generation() 1869 1870 self._analyze_read_write() 1871 self._analyze_calls() 1872 1873 @abstractmethod 1874 def generate_slithir_ssa(self, all_ssa_state_variables_instances): 1875 pass 1876 1877 def update_read_write_using_ssa(self) -> None: 1878 for node in self.nodes: 1879 node.update_read_write_using_ssa() 1880 self._analyze_read_write() 1881 1882 ################################################################################### 1883 ################################################################################### 1884 # region Built in definitions 1885 ################################################################################### 1886 ################################################################################### 1887 1888 def __str__(self) -> str: 1889 return self.name 1890 1891 # endregion
ReacheableNode(node, ir)
56class ModifierStatements: 57 def __init__( 58 self, 59 modifier: Union["Contract", "Function"], 60 entry_point: "Node", 61 nodes: list["Node"], 62 ) -> None: 63 self._modifier = modifier 64 self._entry_point = entry_point 65 self._nodes = nodes 66 67 @property 68 def modifier(self) -> Union["Contract", "Function"]: 69 return self._modifier 70 71 @property 72 def entry_point(self) -> "Node": 73 return self._entry_point 74 75 @entry_point.setter 76 def entry_point(self, entry_point: "Node"): 77 self._entry_point = entry_point 78 79 @property 80 def nodes(self) -> list["Node"]: 81 return self._nodes 82 83 @nodes.setter 84 def nodes(self, nodes: list["Node"]): 85 self._nodes = nodes
88class FunctionType(Enum): 89 NORMAL = 0 90 CONSTRUCTOR = 1 91 FALLBACK = 2 92 RECEIVE = 3 93 CONSTRUCTOR_VARIABLES = 10 # Fake function to hold variable declaration statements 94 CONSTRUCTOR_CONSTANT_VARIABLES = 11 # Fake function to hold variable declaration statements
An enumeration.
An enumeration.
114class Function(SourceMapping, metaclass=ABCMeta): 115 """ 116 Function class 117 """ 118 119 def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None: 120 super().__init__() 121 self._internal_scope: list[str] = [] 122 self._name: str | None = None 123 self._view: bool = False 124 self._pure: bool = False 125 self._payable: bool = False 126 self._visibility: str | None = None 127 self._virtual: bool = False 128 self._overrides: list[FunctionContract] = [] 129 self._overridden_by: list[FunctionContract] = [] 130 131 self._is_implemented: bool | None = None 132 self._is_empty: bool | None = None 133 self._entry_point: Node | None = None 134 self._nodes: list[Node] = [] 135 self._variables: dict[str, LocalVariable] = {} 136 # slithir Temporary and references variables (but not SSA) 137 self._slithir_variables: set[SlithIRVariable] = set() 138 self._parameters: list[LocalVariable] = [] 139 self._parameters_ssa: list[LocalIRVariable] = [] 140 self._parameters_src: SourceMapping = SourceMapping() 141 # This is used for vyper calls with default arguments 142 self._default_args_as_expressions: list[Expression] = [] 143 self._returns: list[LocalVariable] = [] 144 self._returns_ssa: list[LocalIRVariable] = [] 145 self._returns_src: SourceMapping = SourceMapping() 146 self._return_values: list[SlithIRVariable] | None = None 147 self._return_values_ssa: list[SlithIRVariable] | None = None 148 self._vars_read: list[Variable] = [] 149 self._vars_written: list[Variable] = [] 150 self._state_vars_read: list[StateVariable] = [] 151 self._vars_read_or_written: list[Variable] = [] 152 self._solidity_vars_read: list[SolidityVariable] = [] 153 self._state_vars_written: list[StateVariable] = [] 154 self._internal_calls: list[InternalCall] = [] 155 self._solidity_calls: list[SolidityCall] = [] 156 self._low_level_calls: list[LowLevelCall] = [] 157 self._high_level_calls: list[tuple[Contract, HighLevelCall]] = [] 158 self._library_calls: list[LibraryCall] = [] 159 self._external_calls_as_expressions: list[Expression] = [] 160 self._expression_vars_read: list[Expression] = [] 161 self._expression_vars_written: list[Expression] = [] 162 self._expression_calls: list[Expression] = [] 163 # self._expression_modifiers: List["Expression"] = [] 164 self._modifiers: list[ModifierStatements] = [] 165 self._explicit_base_constructor_calls: list[ModifierStatements] = [] 166 self._contains_assembly: bool = False 167 168 self._expressions: list[Expression] | None = None 169 self._slithir_operations: list[Operation] | None = None 170 self._slithir_ssa_operations: list[Operation] | None = None 171 172 self._all_expressions: list[Expression] | None = None 173 self._all_slithir_operations: list[Operation] | None = None 174 self._all_internals_calls: list[InternalCall] | None = None 175 self._all_high_level_calls: list[tuple[Contract, HighLevelCall]] | None = None 176 self._all_library_calls: list[LibraryCall] | None = None 177 self._all_low_level_calls: list[LowLevelCall] | None = None 178 self._all_solidity_calls: list[SolidityCall] | None = None 179 self._all_variables_read: list[Variable] | None = None 180 self._all_variables_written: list[Variable] | None = None 181 self._all_state_variables_read: list[StateVariable] | None = None 182 self._all_solidity_variables_read: list[SolidityVariable] | None = None 183 self._all_state_variables_written: list[StateVariable] | None = None 184 self._all_slithir_variables: list[SlithIRVariable] | None = None 185 self._all_nodes: list[Node] | None = None 186 self._all_conditional_state_variables_read: list[StateVariable] | None = None 187 self._all_conditional_state_variables_read_with_loop: list[StateVariable] | None = None 188 self._all_conditional_solidity_variables_read: list[SolidityVariable] | None = None 189 self._all_conditional_solidity_variables_read_with_loop: list[SolidityVariable] | None = ( 190 None 191 ) 192 self._all_solidity_variables_used_as_args: list[SolidityVariable] | None = None 193 194 self._is_shadowed: bool = False 195 self._shadows: bool = False 196 197 # set(ReacheableNode) 198 self._reachable_from_nodes: set[ReacheableNode] = set() 199 self._reachable_from_functions: set[Function] = set() 200 self._all_reachable_from_functions: set[Function] | None = None 201 202 # Constructor, fallback, State variable constructor 203 self._function_type: FunctionType | None = None 204 self._is_constructor: bool | None = None 205 206 # Computed on the fly, can be True of False 207 self._can_reenter: bool | None = None 208 self._can_send_eth: bool | None = None 209 210 self._nodes_ordered_dominators: list[Node] | None = None 211 212 self._counter_nodes = 0 213 214 # Memoize parameters: 215 # TODO: identify all the memoize parameters and add a way to undo the memoization 216 self._full_name: str | None = None 217 self._signature: tuple[str, list[str], list[str]] | None = None 218 self._solidity_signature: str | None = None 219 self._signature_str: str | None = None 220 self._canonical_name: str | None = None 221 self._is_protected: bool | None = None 222 223 self.compilation_unit: SlitherCompilationUnit = compilation_unit 224 225 self.function_language: FunctionLanguage = ( 226 FunctionLanguage.Solidity if compilation_unit.is_solidity else FunctionLanguage.Vyper 227 ) 228 229 self._id: str | None = None 230 231 # To be improved with a parsing of the documentation 232 self.has_documentation: bool = False 233 234 ################################################################################### 235 ################################################################################### 236 # region General properties 237 ################################################################################### 238 ################################################################################### 239 240 @property 241 def name(self) -> str: 242 """ 243 str: function name 244 """ 245 if self._name == "" and self._function_type == FunctionType.CONSTRUCTOR: 246 return "constructor" 247 if self._name == "" and self._function_type == FunctionType.FALLBACK: 248 return "fallback" 249 if self._function_type == FunctionType.RECEIVE: 250 return "receive" 251 if self._function_type == FunctionType.CONSTRUCTOR_VARIABLES: 252 return "slitherConstructorVariables" 253 if self._function_type == FunctionType.CONSTRUCTOR_CONSTANT_VARIABLES: 254 return "slitherConstructorConstantVariables" 255 return self._name 256 257 @name.setter 258 def name(self, new_name: str): 259 self._name = new_name 260 261 @property 262 def internal_scope(self) -> list[str]: 263 """ 264 Return a list of name representing the scope of the function 265 This is used to model nested functions declared in YUL 266 267 :return: 268 """ 269 return self._internal_scope 270 271 @internal_scope.setter 272 def internal_scope(self, new_scope: list[str]): 273 self._internal_scope = new_scope 274 275 @property 276 def full_name(self) -> str: 277 """ 278 str: func_name(type1,type2) 279 Return the function signature without the return values 280 The difference between this function and solidity_function is that full_name does not translate the underlying 281 type (ex: structure, contract to address, ...) 282 """ 283 if self._full_name is None: 284 name, parameters, _ = self.signature 285 full_name = ".".join(self._internal_scope + [name]) + "(" + ",".join(parameters) + ")" 286 self._full_name = full_name 287 return self._full_name 288 289 @property 290 @abstractmethod 291 def canonical_name(self) -> str: 292 """ 293 str: contract.func_name(type1,type2) 294 Return the function signature without the return values 295 """ 296 return "" 297 298 @property 299 def contains_assembly(self) -> bool: 300 return self._contains_assembly 301 302 @contains_assembly.setter 303 def contains_assembly(self, c: bool): 304 self._contains_assembly = c 305 306 def can_reenter(self, callstack: list[Union["Function", "Variable"]] | None = None) -> bool: 307 """ 308 Check if the function can re-enter 309 Follow internal calls. 310 Do not consider CREATE as potential re-enter, but check if the 311 destination's constructor can contain a call (recurs. follow nested CREATE) 312 For Solidity > 0.5, filter access to public variables and constant/pure/view 313 For call to this. check if the destination can re-enter 314 Do not consider Send/Transfer as there is not enough gas 315 :param callstack: used internally to check for recursion 316 :return bool: 317 """ 318 from slither.slithir.operations import Call 319 320 if self._can_reenter is None: 321 self._can_reenter = False 322 for ir in self.all_slithir_operations(): 323 if isinstance(ir, Call) and ir.can_reenter(callstack): 324 self._can_reenter = True 325 return True 326 return self._can_reenter 327 328 def can_send_eth(self) -> bool: 329 """ 330 Check if the function or any internal (not external) functions called by it can send eth 331 :return bool: 332 """ 333 from slither.slithir.operations import Call 334 335 if self._can_send_eth is None: 336 self._can_send_eth = False 337 for ir in self.all_slithir_operations(): 338 if isinstance(ir, Call) and ir.can_send_eth(): 339 self._can_send_eth = True 340 return True 341 return self._can_send_eth 342 343 @property 344 def is_checked(self) -> bool: 345 """ 346 Return true if the overflow are enabled by default 347 348 349 :return: 350 """ 351 352 return self.compilation_unit.solc_version >= "0.8.0" 353 354 @property 355 def id(self) -> str | None: 356 """ 357 Return the reference ID of the function, if available. 358 359 :return: 360 :rtype: 361 """ 362 return self._id 363 364 @id.setter 365 def id(self, new_id: str): 366 self._id = new_id 367 368 @property 369 @abstractmethod 370 def file_scope(self) -> "FileScope": 371 pass 372 373 # endregion 374 ################################################################################### 375 ################################################################################### 376 # region Type (FunctionType) 377 ################################################################################### 378 ################################################################################### 379 380 def set_function_type(self, t: FunctionType) -> None: 381 assert isinstance(t, FunctionType) 382 self._function_type = t 383 384 @property 385 def function_type(self) -> FunctionType | None: 386 return self._function_type 387 388 @function_type.setter 389 def function_type(self, t: FunctionType): 390 self._function_type = t 391 392 @property 393 def is_constructor(self) -> bool: 394 """ 395 bool: True if the function is the constructor 396 """ 397 return self._function_type == FunctionType.CONSTRUCTOR 398 399 @property 400 def is_constructor_variables(self) -> bool: 401 """ 402 bool: True if the function is the constructor of the variables 403 Slither has inbuilt functions to hold the state variables initialization 404 """ 405 return self._function_type in [ 406 FunctionType.CONSTRUCTOR_VARIABLES, 407 FunctionType.CONSTRUCTOR_CONSTANT_VARIABLES, 408 ] 409 410 @property 411 def is_fallback(self) -> bool: 412 """ 413 Determine if the function is the fallback function for the contract 414 Returns 415 (bool) 416 """ 417 return self._function_type == FunctionType.FALLBACK 418 419 @property 420 def is_receive(self) -> bool: 421 """ 422 Determine if the function is the receive function for the contract 423 Returns 424 (bool) 425 """ 426 return self._function_type == FunctionType.RECEIVE 427 428 # endregion 429 ################################################################################### 430 ################################################################################### 431 # region Payable 432 ################################################################################### 433 ################################################################################### 434 435 @property 436 def payable(self) -> bool: 437 """ 438 bool: True if the function is payable 439 """ 440 return self._payable 441 442 @payable.setter 443 def payable(self, p: bool): 444 self._payable = p 445 446 # endregion 447 ################################################################################### 448 ################################################################################### 449 # region Virtual 450 ################################################################################### 451 ################################################################################### 452 453 @property 454 def is_virtual(self) -> bool: 455 """ 456 Note for Solidity < 0.6.0 it will always be false 457 bool: True if the function is virtual 458 """ 459 return self._virtual 460 461 @is_virtual.setter 462 def is_virtual(self, v: bool): 463 self._virtual = v 464 465 @property 466 def is_override(self) -> bool: 467 """ 468 Note for Solidity < 0.6.0 it will always be false 469 bool: True if the function overrides a base function 470 """ 471 return len(self._overrides) > 0 472 473 @property 474 def overridden_by(self) -> list["FunctionContract"]: 475 """ 476 List["FunctionContract"]: List of functions in child contracts that override this function 477 This may include distinct instances of the same function due to inheritance 478 """ 479 return self._overridden_by 480 481 @property 482 def overrides(self) -> list["FunctionContract"]: 483 """ 484 List["FunctionContract"]: List of functions in parent contracts that this function overrides 485 This may include distinct instances of the same function due to inheritance 486 """ 487 return self._overrides 488 489 # endregion 490 ################################################################################### 491 ################################################################################### 492 # region Visibility 493 ################################################################################### 494 ################################################################################### 495 496 @property 497 def visibility(self) -> str: 498 """ 499 str: Function visibility 500 """ 501 assert self._visibility is not None 502 return self._visibility 503 504 @visibility.setter 505 def visibility(self, v: str): 506 self._visibility = v 507 508 def set_visibility(self, v: str) -> None: 509 self._visibility = v 510 511 @property 512 def view(self) -> bool: 513 """ 514 bool: True if the function is declared as view 515 """ 516 return self._view 517 518 @view.setter 519 def view(self, v: bool): 520 self._view = v 521 522 @property 523 def pure(self) -> bool: 524 """ 525 bool: True if the function is declared as pure 526 """ 527 return self._pure 528 529 @pure.setter 530 def pure(self, p: bool): 531 self._pure = p 532 533 @property 534 def is_shadowed(self) -> bool: 535 return self._is_shadowed 536 537 @is_shadowed.setter 538 def is_shadowed(self, is_shadowed): 539 self._is_shadowed = is_shadowed 540 541 @property 542 def shadows(self) -> bool: 543 return self._shadows 544 545 @shadows.setter 546 def shadows(self, _shadows: bool): 547 self._shadows = _shadows 548 549 # endregion 550 ################################################################################### 551 ################################################################################### 552 # region Function's body 553 ################################################################################### 554 ################################################################################### 555 556 @property 557 def is_implemented(self) -> bool: 558 """ 559 bool: True if the function is implemented 560 """ 561 return self._is_implemented 562 563 @is_implemented.setter 564 def is_implemented(self, is_impl: bool): 565 self._is_implemented = is_impl 566 567 @property 568 def is_empty(self) -> bool: 569 """ 570 bool: True if the function is empty, None if the function is an interface 571 """ 572 return self._is_empty 573 574 @is_empty.setter 575 def is_empty(self, empty: bool): 576 self._is_empty = empty 577 578 # endregion 579 ################################################################################### 580 ################################################################################### 581 # region Nodes 582 ################################################################################### 583 ################################################################################### 584 585 @property 586 def nodes(self) -> list["Node"]: 587 """ 588 list(Node): List of the nodes 589 """ 590 return list(self._nodes) 591 592 @nodes.setter 593 def nodes(self, nodes: list["Node"]): 594 self._nodes = nodes 595 596 @property 597 def entry_point(self) -> Optional["Node"]: 598 """ 599 Node: Entry point of the function 600 """ 601 return self._entry_point 602 603 @entry_point.setter 604 def entry_point(self, node: "Node"): 605 self._entry_point = node 606 607 def add_node(self, node: "Node") -> None: 608 if not self._entry_point: 609 self._entry_point = node 610 self._nodes.append(node) 611 612 @property 613 def nodes_ordered_dominators(self) -> list["Node"]: 614 # TODO: does not work properly; most likely due to modifier call 615 # This will not work for modifier call that lead to multiple nodes 616 # from slither.core.cfg.node import NodeType 617 if self._nodes_ordered_dominators is None: 618 self._nodes_ordered_dominators = [] 619 if self.entry_point: 620 self._compute_nodes_ordered_dominators(self.entry_point) 621 622 for node in self.nodes: 623 # if node.type == NodeType.OTHER_ENTRYPOINT: 624 if node not in self._nodes_ordered_dominators: 625 self._compute_nodes_ordered_dominators(node) 626 627 return self._nodes_ordered_dominators 628 629 def _compute_nodes_ordered_dominators(self, node: "Node"): 630 assert self._nodes_ordered_dominators is not None 631 if node in self._nodes_ordered_dominators: 632 return 633 self._nodes_ordered_dominators.append(node) 634 for dom in node.dominance_exploration_ordered: 635 self._compute_nodes_ordered_dominators(dom) 636 637 # endregion 638 ################################################################################### 639 ################################################################################### 640 # region Parameters 641 ################################################################################### 642 ################################################################################### 643 644 @property 645 def parameters(self) -> list["LocalVariable"]: 646 """ 647 list(LocalVariable): List of the parameters 648 """ 649 return list(self._parameters) 650 651 def add_parameters(self, p: "LocalVariable") -> None: 652 self._parameters.append(p) 653 654 @property 655 def parameters_ssa(self) -> list["LocalIRVariable"]: 656 """ 657 list(LocalIRVariable): List of the parameters (SSA form) 658 """ 659 return list(self._parameters_ssa) 660 661 def add_parameter_ssa(self, var: "LocalIRVariable") -> None: 662 self._parameters_ssa.append(var) 663 664 def parameters_src(self) -> SourceMapping: 665 return self._parameters_src 666 667 # endregion 668 ################################################################################### 669 ################################################################################### 670 # region Return values 671 ################################################################################### 672 ################################################################################### 673 674 @property 675 def return_type(self) -> list[Type] | None: 676 """ 677 Return the list of return type 678 If no return, return None 679 """ 680 returns = self.returns 681 if returns: 682 return [r.type for r in returns] 683 return None 684 685 def returns_src(self) -> SourceMapping: 686 return self._returns_src 687 688 @property 689 def type(self) -> list[Type] | None: 690 """ 691 Return the list of return type 692 If no return, return None 693 Alias of return_type 694 """ 695 return self.return_type 696 697 @property 698 def returns(self) -> list["LocalVariable"]: 699 """ 700 list(LocalVariable): List of the return variables 701 """ 702 return list(self._returns) 703 704 def add_return(self, r: "LocalVariable") -> None: 705 self._returns.append(r) 706 707 @property 708 def returns_ssa(self) -> list["LocalIRVariable"]: 709 """ 710 list(LocalIRVariable): List of the return variables (SSA form) 711 """ 712 return list(self._returns_ssa) 713 714 def add_return_ssa(self, var: "LocalIRVariable") -> None: 715 self._returns_ssa.append(var) 716 717 # endregion 718 ################################################################################### 719 ################################################################################### 720 # region Modifiers 721 ################################################################################### 722 ################################################################################### 723 724 @property 725 def modifiers(self) -> list[Union["Contract", "Function"]]: 726 """ 727 list(Modifier): List of the modifiers 728 Can be contract for constructor's calls 729 730 """ 731 return [c.modifier for c in self._modifiers] 732 733 def add_modifier(self, modif: "ModifierStatements") -> None: 734 self._modifiers.append(modif) 735 736 @property 737 def modifiers_statements(self) -> list[ModifierStatements]: 738 """ 739 list(ModifierCall): List of the modifiers call (include expression and irs) 740 """ 741 return list(self._modifiers) 742 743 @property 744 def explicit_base_constructor_calls(self) -> list["Function"]: 745 """ 746 list(Function): List of the base constructors called explicitly by this presumed constructor definition. 747 748 Base constructors implicitly or explicitly called by the contract definition will not be 749 included. 750 """ 751 # This is a list of contracts internally, so we convert it to a list of constructor functions. 752 return [ 753 c.modifier.constructors_declared 754 for c in self._explicit_base_constructor_calls 755 if c.modifier.constructors_declared 756 ] 757 758 @property 759 def explicit_base_constructor_calls_statements(self) -> list[ModifierStatements]: 760 """ 761 list(ModifierCall): List of the base constructors called explicitly by this presumed constructor definition. 762 763 """ 764 # This is a list of contracts internally, so we convert it to a list of constructor functions. 765 return list(self._explicit_base_constructor_calls) 766 767 def add_explicit_base_constructor_calls_statements(self, modif: ModifierStatements) -> None: 768 self._explicit_base_constructor_calls.append(modif) 769 770 # endregion 771 ################################################################################### 772 ################################################################################### 773 # region Variables 774 ################################################################################### 775 ################################################################################### 776 777 @property 778 def variables(self) -> list[LocalVariable]: 779 """ 780 Return all local variables 781 Include parameters and return values 782 """ 783 return list(self._variables.values()) 784 785 @property 786 def local_variables(self) -> list[LocalVariable]: 787 """ 788 Return all local variables (dont include parameters and return values) 789 """ 790 return list(set(self.variables) - set(self.returns) - set(self.parameters)) 791 792 @property 793 def variables_as_dict(self) -> dict[str, LocalVariable]: 794 return self._variables 795 796 @property 797 def variables_read(self) -> list["Variable"]: 798 """ 799 list(Variable): Variables read (local/state/solidity) 800 """ 801 return list(self._vars_read) 802 803 @property 804 def variables_written(self) -> list["Variable"]: 805 """ 806 list(Variable): Variables written (local/state/solidity) 807 """ 808 return list(self._vars_written) 809 810 @property 811 def state_variables_read(self) -> list["StateVariable"]: 812 """ 813 list(StateVariable): State variables read 814 """ 815 return list(self._state_vars_read) 816 817 @property 818 def solidity_variables_read(self) -> list["SolidityVariable"]: 819 """ 820 list(SolidityVariable): Solidity variables read 821 """ 822 return list(self._solidity_vars_read) 823 824 @property 825 def state_variables_written(self) -> list["StateVariable"]: 826 """ 827 list(StateVariable): State variables written 828 """ 829 return list(self._state_vars_written) 830 831 @property 832 def variables_read_or_written(self) -> list["Variable"]: 833 """ 834 list(Variable): Variables read or written (local/state/solidity) 835 """ 836 return list(self._vars_read_or_written) 837 838 @property 839 def variables_read_as_expression(self) -> list["Expression"]: 840 return self._expression_vars_read 841 842 @property 843 def variables_written_as_expression(self) -> list["Expression"]: 844 return self._expression_vars_written 845 846 @property 847 def slithir_variables(self) -> list["SlithIRVariable"]: 848 """ 849 Temporary and Reference Variables (not SSA form) 850 """ 851 852 return list(self._slithir_variables) 853 854 # endregion 855 ################################################################################### 856 ################################################################################### 857 # region Calls 858 ################################################################################### 859 ################################################################################### 860 861 @property 862 def internal_calls(self) -> list["InternalCall"]: 863 """ 864 list(InternalCall): List of IR operations for internal calls 865 """ 866 return list(self._internal_calls) 867 868 @property 869 def solidity_calls(self) -> list["SolidityCall"]: 870 """ 871 list(SolidityCall): List of IR operations for Solidity calls 872 """ 873 return list(self._solidity_calls) 874 875 @property 876 def high_level_calls(self) -> list[tuple["Contract", "HighLevelCall"]]: 877 """ 878 list(Tuple(Contract, "HighLevelCall")): List of call target contract and IR of the high level call 879 A variable is called in case of call to a public state variable 880 Include library calls 881 """ 882 return list(self._high_level_calls) 883 884 @property 885 def library_calls(self) -> list["LibraryCall"]: 886 """ 887 list(LibraryCall): List of IR operations for library calls 888 """ 889 return list(self._library_calls) 890 891 @property 892 def low_level_calls(self) -> list["LowLevelCall"]: 893 """ 894 list(LowLevelCall): List of IR operations for low level calls 895 A low level call is defined by 896 - the variable called 897 - the name of the function (call/delegatecall/callcode) 898 """ 899 return list(self._low_level_calls) 900 901 @property 902 def external_calls_as_expressions(self) -> list["Expression"]: 903 """ 904 list(ExpressionCall): List of message calls (that creates a transaction) 905 """ 906 return list(self._external_calls_as_expressions) 907 908 # endregion 909 ################################################################################### 910 ################################################################################### 911 # region Expressions 912 ################################################################################### 913 ################################################################################### 914 915 @property 916 def calls_as_expressions(self) -> list["Expression"]: 917 return self._expression_calls 918 919 @property 920 def expressions(self) -> list["Expression"]: 921 """ 922 list(Expression): List of the expressions 923 """ 924 if self._expressions is None: 925 expressionss = [n.expression for n in self.nodes] 926 expressions = [e for e in expressionss if e] 927 self._expressions = expressions 928 return self._expressions 929 930 @property 931 def return_values(self) -> list["SlithIRVariable"]: 932 """ 933 list(Return Values): List of the return values 934 """ 935 from slither.core.cfg.node import NodeType 936 from slither.slithir.operations import Return 937 from slither.slithir.variables import Constant 938 939 if self._return_values is None: 940 return_values = [] 941 returns = [n for n in self.nodes if n.type == NodeType.RETURN] 942 [ 943 return_values.extend(ir.values) 944 for node in returns 945 for ir in node.irs 946 if isinstance(ir, Return) 947 ] 948 self._return_values = list({x for x in return_values if not isinstance(x, Constant)}) 949 return self._return_values 950 951 @property 952 def return_values_ssa(self) -> list["SlithIRVariable"]: 953 """ 954 list(Return Values in SSA form): List of the return values in ssa form 955 """ 956 from slither.core.cfg.node import NodeType 957 from slither.slithir.operations import Return 958 from slither.slithir.variables import Constant 959 960 if self._return_values_ssa is None: 961 return_values_ssa = [] 962 returns = [n for n in self.nodes if n.type == NodeType.RETURN] 963 [ 964 return_values_ssa.extend(ir.values) 965 for node in returns 966 for ir in node.irs_ssa 967 if isinstance(ir, Return) 968 ] 969 self._return_values_ssa = list( 970 {x for x in return_values_ssa if not isinstance(x, Constant)} 971 ) 972 return self._return_values_ssa 973 974 # endregion 975 ################################################################################### 976 ################################################################################### 977 # region SlithIR 978 ################################################################################### 979 ################################################################################### 980 981 @property 982 def slithir_operations(self) -> list["Operation"]: 983 """ 984 list(Operation): List of the slithir operations 985 """ 986 if self._slithir_operations is None: 987 operationss = [n.irs for n in self.nodes] 988 operations = [item for sublist in operationss for item in sublist if item] 989 self._slithir_operations = operations 990 return self._slithir_operations 991 992 @property 993 def slithir_ssa_operations(self) -> list["Operation"]: 994 """ 995 list(Operation): List of the slithir operations (SSA) 996 """ 997 if self._slithir_ssa_operations is None: 998 operationss = [n.irs_ssa for n in self.nodes] 999 operations = [item for sublist in operationss for item in sublist if item] 1000 self._slithir_ssa_operations = operations 1001 return self._slithir_ssa_operations 1002 1003 # endregion 1004 ################################################################################### 1005 ################################################################################### 1006 # region Signature 1007 ################################################################################### 1008 ################################################################################### 1009 1010 @property 1011 def solidity_signature(self) -> str: 1012 """ 1013 Return a signature following the Solidity Standard 1014 Contract and converted into address 1015 1016 It might still keep internal types (ex: structure name) for internal functions. 1017 The reason is that internal functions allows recursive structure definition, which 1018 can't be converted following the Solidity stand ard 1019 1020 :return: the solidity signature 1021 """ 1022 if self._solidity_signature is None: 1023 parameters = [ 1024 convert_type_for_solidity_signature_to_string(x.type) for x in self.parameters 1025 ] 1026 self._solidity_signature = self.name + "(" + ",".join(parameters) + ")" 1027 return self._solidity_signature 1028 1029 @property 1030 def signature(self) -> tuple[str, list[str], list[str]]: 1031 """ 1032 (str, list(str), list(str)): Function signature as 1033 (name, list parameters type, list return values type) 1034 """ 1035 # FIXME memoizing this function is not working properly for vyper 1036 # if self._signature is None: 1037 return ( 1038 self.name, 1039 [str(x.type) for x in self.parameters], 1040 [str(x.type) for x in self.returns], 1041 ) 1042 # self._signature = signature 1043 # return self._signature 1044 1045 @property 1046 def signature_str(self) -> str: 1047 """ 1048 str: func_name(type1,type2) returns (type3) 1049 Return the function signature as a str (contains the return values) 1050 """ 1051 if self._signature_str is None: 1052 name, parameters, returnVars = self.signature 1053 self._signature_str = ( 1054 name + "(" + ",".join(parameters) + ") returns(" + ",".join(returnVars) + ")" 1055 ) 1056 return self._signature_str 1057 1058 # endregion 1059 ################################################################################### 1060 ################################################################################### 1061 # region Functions 1062 ################################################################################### 1063 ################################################################################### 1064 1065 @property 1066 @abstractmethod 1067 def functions_shadowed(self) -> list["Function"]: 1068 pass 1069 1070 # endregion 1071 ################################################################################### 1072 ################################################################################### 1073 # region Reachable 1074 ################################################################################### 1075 ################################################################################### 1076 1077 @property 1078 def reachable_from_nodes(self) -> set[ReacheableNode]: 1079 """ 1080 Return 1081 ReacheableNode 1082 """ 1083 return self._reachable_from_nodes 1084 1085 @property 1086 def reachable_from_functions(self) -> set["Function"]: 1087 return self._reachable_from_functions 1088 1089 @property 1090 def all_reachable_from_functions(self) -> set["Function"]: 1091 """ 1092 Give the recursive version of reachable_from_functions (all the functions that lead to call self in the CFG) 1093 """ 1094 if self._all_reachable_from_functions is None: 1095 functions: set[Function] = set() 1096 1097 new_functions = self.reachable_from_functions 1098 # iterate until we have are finding new functions 1099 while new_functions and not new_functions.issubset(functions): 1100 functions = functions.union(new_functions) 1101 # Use a temporary set, because we iterate over new_functions 1102 new_functionss: set[Function] = set() 1103 for f in new_functions: 1104 new_functionss = new_functionss.union(f.reachable_from_functions) 1105 new_functions = new_functionss - functions 1106 1107 self._all_reachable_from_functions = functions 1108 return self._all_reachable_from_functions 1109 1110 def add_reachable_from_node(self, n: "Node", ir: "Operation") -> None: 1111 self._reachable_from_nodes.add(ReacheableNode(n, ir)) 1112 self._reachable_from_functions.add(n.function) 1113 1114 # endregion 1115 ################################################################################### 1116 ################################################################################### 1117 # region Recursive getters 1118 ################################################################################### 1119 ################################################################################### 1120 1121 def _explore_functions(self, f_new_values: Callable[["Function"], list]) -> list[Any]: 1122 values = f_new_values(self) 1123 explored: set[Function] = {self} 1124 to_explore_set: set[Function] = set() 1125 1126 for ir in self.internal_calls: 1127 if isinstance(ir.function, Function) and ir.function not in explored: 1128 to_explore_set.add(ir.function) 1129 for ir in self.library_calls: 1130 if isinstance(ir.function, Function) and ir.function not in explored: 1131 to_explore_set.add(ir.function) 1132 for m in self.modifiers: 1133 if m not in explored: 1134 to_explore_set.add(m) 1135 1136 while to_explore_set: 1137 f = to_explore_set.pop() 1138 if f in explored: 1139 continue 1140 explored.add(f) 1141 1142 values += f_new_values(f) 1143 1144 for ir in f.internal_calls: 1145 if isinstance(ir.function, Function) and ir.function not in explored: 1146 to_explore_set.add(ir.function) 1147 for ir in f.library_calls: 1148 if isinstance(ir.function, Function) and ir.function not in explored: 1149 to_explore_set.add(ir.function) 1150 for m in f.modifiers: 1151 if m not in explored: 1152 to_explore_set.add(m) 1153 1154 return list(set(values)) 1155 1156 def all_variables_read(self) -> list["Variable"]: 1157 """recursive version of variables_read""" 1158 if self._all_variables_read is None: 1159 self._all_variables_read = self._explore_functions(lambda x: x.variables_read) 1160 return self._all_variables_read 1161 1162 def all_variables_written(self) -> list["Variable"]: 1163 """recursive version of variables_written""" 1164 if self._all_variables_written is None: 1165 self._all_variables_written = self._explore_functions(lambda x: x.variables_written) 1166 return self._all_variables_written 1167 1168 def all_state_variables_read(self) -> list["StateVariable"]: 1169 """recursive version of variables_read""" 1170 if self._all_state_variables_read is None: 1171 self._all_state_variables_read = self._explore_functions( 1172 lambda x: x.state_variables_read 1173 ) 1174 return self._all_state_variables_read 1175 1176 def all_solidity_variables_read(self) -> list[SolidityVariable]: 1177 """recursive version of solidity_read""" 1178 if self._all_solidity_variables_read is None: 1179 self._all_solidity_variables_read = self._explore_functions( 1180 lambda x: x.solidity_variables_read 1181 ) 1182 return self._all_solidity_variables_read 1183 1184 def all_slithir_variables(self) -> list["SlithIRVariable"]: 1185 """recursive version of slithir_variables""" 1186 if self._all_slithir_variables is None: 1187 self._all_slithir_variables = self._explore_functions(lambda x: x.slithir_variables) 1188 return self._all_slithir_variables 1189 1190 def all_nodes(self) -> list["Node"]: 1191 """recursive version of nodes""" 1192 if self._all_nodes is None: 1193 self._all_nodes = self._explore_functions(lambda x: x.nodes) 1194 return self._all_nodes 1195 1196 def all_expressions(self) -> list["Expression"]: 1197 """recursive version of variables_read""" 1198 if self._all_expressions is None: 1199 self._all_expressions = self._explore_functions(lambda x: x.expressions) 1200 return self._all_expressions 1201 1202 def all_slithir_operations(self) -> list["Operation"]: 1203 if self._all_slithir_operations is None: 1204 self._all_slithir_operations = self._explore_functions(lambda x: x.slithir_operations) 1205 return self._all_slithir_operations 1206 1207 def all_state_variables_written(self) -> list[StateVariable]: 1208 """recursive version of variables_written""" 1209 if self._all_state_variables_written is None: 1210 self._all_state_variables_written = self._explore_functions( 1211 lambda x: x.state_variables_written 1212 ) 1213 return self._all_state_variables_written 1214 1215 def all_internal_calls(self) -> list["InternalCall"]: 1216 """recursive version of internal_calls""" 1217 if self._all_internals_calls is None: 1218 self._all_internals_calls = self._explore_functions(lambda x: x.internal_calls) 1219 return self._all_internals_calls 1220 1221 def all_low_level_calls(self) -> list["LowLevelCall"]: 1222 """recursive version of low_level calls""" 1223 if self._all_low_level_calls is None: 1224 self._all_low_level_calls = self._explore_functions(lambda x: x.low_level_calls) 1225 return self._all_low_level_calls 1226 1227 def all_high_level_calls(self) -> list[tuple["Contract", "HighLevelCall"]]: 1228 """recursive version of high_level calls""" 1229 if self._all_high_level_calls is None: 1230 self._all_high_level_calls = self._explore_functions(lambda x: x.high_level_calls) 1231 return self._all_high_level_calls 1232 1233 def all_library_calls(self) -> list["LibraryCall"]: 1234 """recursive version of library calls""" 1235 if self._all_library_calls is None: 1236 self._all_library_calls = self._explore_functions(lambda x: x.library_calls) 1237 return self._all_library_calls 1238 1239 def all_solidity_calls(self) -> list["SolidityCall"]: 1240 """recursive version of solidity calls""" 1241 if self._all_solidity_calls is None: 1242 self._all_solidity_calls = self._explore_functions(lambda x: x.solidity_calls) 1243 return self._all_solidity_calls 1244 1245 @staticmethod 1246 def _explore_func_cond_read(func: "Function", include_loop: bool) -> list["StateVariable"]: 1247 ret = [n.state_variables_read for n in func.nodes if n.is_conditional(include_loop)] 1248 return [item for sublist in ret for item in sublist] 1249 1250 def all_conditional_state_variables_read(self, include_loop=True) -> list["StateVariable"]: 1251 """ 1252 Return the state variable used in a condition 1253 1254 Over approximate and also return index access 1255 It won't work if the variable is assigned to a temp variable 1256 """ 1257 if include_loop: 1258 if self._all_conditional_state_variables_read_with_loop is None: 1259 self._all_conditional_state_variables_read_with_loop = self._explore_functions( 1260 lambda x: self._explore_func_cond_read(x, include_loop) 1261 ) 1262 return self._all_conditional_state_variables_read_with_loop 1263 if self._all_conditional_state_variables_read is None: 1264 self._all_conditional_state_variables_read = self._explore_functions( 1265 lambda x: self._explore_func_cond_read(x, include_loop) 1266 ) 1267 return self._all_conditional_state_variables_read 1268 1269 @staticmethod 1270 def _solidity_variable_in_binary(node: "Node") -> list[SolidityVariable]: 1271 from slither.slithir.operations.binary import Binary 1272 1273 ret = [] 1274 for ir in node.irs: 1275 if isinstance(ir, Binary): 1276 ret += ir.read 1277 return [var for var in ret if isinstance(var, SolidityVariable)] 1278 1279 @staticmethod 1280 def _explore_func_conditional( 1281 func: "Function", 1282 f: Callable[["Node"], list[SolidityVariable]], 1283 include_loop: bool, 1284 ) -> list[Any]: 1285 ret = [f(n) for n in func.nodes if n.is_conditional(include_loop)] 1286 return [item for sublist in ret for item in sublist] 1287 1288 def all_conditional_solidity_variables_read( 1289 self, include_loop: bool = True 1290 ) -> list[SolidityVariable]: 1291 """ 1292 Return the Solidity variables directly used in a condition 1293 1294 Use of the IR to filter index access 1295 Assumption: the solidity vars are used directly in the conditional node 1296 It won't work if the variable is assigned to a temp variable 1297 """ 1298 if include_loop: 1299 if self._all_conditional_solidity_variables_read_with_loop is None: 1300 self._all_conditional_solidity_variables_read_with_loop = self._explore_functions( 1301 lambda x: self._explore_func_conditional( 1302 x, self._solidity_variable_in_binary, include_loop 1303 ) 1304 ) 1305 return self._all_conditional_solidity_variables_read_with_loop 1306 1307 if self._all_conditional_solidity_variables_read is None: 1308 self._all_conditional_solidity_variables_read = self._explore_functions( 1309 lambda x: self._explore_func_conditional( 1310 x, self._solidity_variable_in_binary, include_loop 1311 ) 1312 ) 1313 return self._all_conditional_solidity_variables_read 1314 1315 @staticmethod 1316 def _solidity_variable_in_internal_calls(node: "Node") -> list[SolidityVariable]: 1317 from slither.slithir.operations.internal_call import InternalCall 1318 1319 ret = [] 1320 for ir in node.irs: 1321 if isinstance(ir, InternalCall): 1322 ret += ir.read 1323 return [var for var in ret if isinstance(var, SolidityVariable)] 1324 1325 @staticmethod 1326 def _explore_func_nodes( 1327 func: "Function", f: Callable[["Node"], list[SolidityVariable]] 1328 ) -> list[Any | SolidityVariableComposed]: 1329 ret = [f(n) for n in func.nodes] 1330 return [item for sublist in ret for item in sublist] 1331 1332 def all_solidity_variables_used_as_args(self) -> list[SolidityVariable]: 1333 """ 1334 Return the Solidity variables directly used in a call 1335 1336 Use of the IR to filter index access 1337 Used to catch check(msg.sender) 1338 """ 1339 if self._all_solidity_variables_used_as_args is None: 1340 self._all_solidity_variables_used_as_args = self._explore_functions( 1341 lambda x: self._explore_func_nodes(x, self._solidity_variable_in_internal_calls) 1342 ) 1343 return self._all_solidity_variables_used_as_args 1344 1345 # endregion 1346 ################################################################################### 1347 ################################################################################### 1348 # region Visitor 1349 ################################################################################### 1350 ################################################################################### 1351 1352 def apply_visitor(self, Visitor: Callable) -> list: 1353 """ 1354 Apply a visitor to all the function expressions 1355 Args: 1356 Visitor: slither.visitors 1357 Returns 1358 list(): results of the visit 1359 """ 1360 expressions = self.expressions 1361 v = [Visitor(e).result() for e in expressions] 1362 return [item for sublist in v for item in sublist] 1363 1364 # endregion 1365 ################################################################################### 1366 ################################################################################### 1367 # region Getters from/to object 1368 ################################################################################### 1369 ################################################################################### 1370 1371 def get_local_variable_from_name(self, variable_name: str) -> LocalVariable | None: 1372 """ 1373 Return a local variable from a name 1374 1375 Args: 1376 variable_name (str): name of the variable 1377 Returns: 1378 LocalVariable 1379 """ 1380 return next((v for v in self.variables if v.name == variable_name), None) 1381 1382 # endregion 1383 ################################################################################### 1384 ################################################################################### 1385 # region Export 1386 ################################################################################### 1387 ################################################################################### 1388 1389 def cfg_to_dot(self, filename: str): 1390 """ 1391 Export the function to a dot file 1392 Args: 1393 filename (str) 1394 """ 1395 with open(filename, "w", encoding="utf8") as f: 1396 f.write("digraph{\n") 1397 for node in self.nodes: 1398 f.write(f'{node.node_id}[label="{node!s}"];\n') 1399 for son in node.sons: 1400 f.write(f"{node.node_id}->{son.node_id};\n") 1401 1402 f.write("}\n") 1403 1404 def dominator_tree_to_dot(self, filename: str): 1405 """ 1406 Export the dominator tree of the function to a dot file 1407 Args: 1408 filename (str) 1409 """ 1410 1411 def description(node): 1412 desc = f"{node}\n" 1413 desc += f"id: {node.node_id}" 1414 if node.dominance_frontier: 1415 desc += f"\ndominance frontier: {[n.node_id for n in node.dominance_frontier]}" 1416 return desc 1417 1418 with open(filename, "w", encoding="utf8") as f: 1419 f.write("digraph{\n") 1420 for node in self.nodes: 1421 f.write(f'{node.node_id}[label="{description(node)}"];\n') 1422 if node.immediate_dominator: 1423 f.write(f"{node.immediate_dominator.node_id}->{node.node_id};\n") 1424 1425 f.write("}\n") 1426 1427 def slithir_cfg_to_dot(self, filename: str): 1428 """ 1429 Export the CFG to a DOT file. The nodes includes the Solidity expressions and the IRs 1430 :param filename: 1431 :return: 1432 """ 1433 content = self.slithir_cfg_to_dot_str() 1434 with open(filename, "w", encoding="utf8") as f: 1435 f.write(content) 1436 1437 def slithir_cfg_to_dot_str(self, skip_expressions: bool = False) -> str: 1438 """ 1439 Export the CFG to a DOT format. The nodes includes the Solidity expressions and the IRs 1440 :return: the DOT content 1441 :rtype: str 1442 """ 1443 from slither.core.cfg.node import NodeType 1444 1445 content = "" 1446 content += "digraph{\n" 1447 for node in self.nodes: 1448 label = f"Node Type: {node.type.value} {node.node_id}\n" 1449 if node.expression and not skip_expressions: 1450 label += f"\nEXPRESSION:\n{node.expression}\n" 1451 if node.irs and not skip_expressions: 1452 label += "\nIRs:\n" + "\n".join([str(ir) for ir in node.irs]) 1453 content += f'{node.node_id}[label="{label}"];\n' 1454 if node.type in [NodeType.IF, NodeType.IFLOOP]: 1455 true_node = node.son_true 1456 if true_node: 1457 content += f'{node.node_id}->{true_node.node_id}[label="True"];\n' 1458 false_node = node.son_false 1459 if false_node: 1460 content += f'{node.node_id}->{false_node.node_id}[label="False"];\n' 1461 else: 1462 for son in node.sons: 1463 content += f"{node.node_id}->{son.node_id};\n" 1464 1465 content += "}\n" 1466 return content 1467 1468 # endregion 1469 ################################################################################### 1470 ################################################################################### 1471 # region Summary information 1472 ################################################################################### 1473 ################################################################################### 1474 1475 def is_reading(self, variable: "Variable") -> bool: 1476 """ 1477 Check if the function reads the variable 1478 Args: 1479 variable (Variable): 1480 Returns: 1481 bool: True if the variable is read 1482 """ 1483 return variable in self.variables_read 1484 1485 def is_reading_in_conditional_node(self, variable: "Variable") -> bool: 1486 """ 1487 Check if the function reads the variable in a IF node 1488 Args: 1489 variable (Variable): 1490 Returns: 1491 bool: True if the variable is read 1492 """ 1493 variables_reads = [n.variables_read for n in self.nodes if n.contains_if()] 1494 variables_read = [item for sublist in variables_reads for item in sublist] 1495 return variable in variables_read 1496 1497 def is_reading_in_require_or_assert(self, variable: "Variable") -> bool: 1498 """ 1499 Check if the function reads the variable in an require or assert 1500 Args: 1501 variable (Variable): 1502 Returns: 1503 bool: True if the variable is read 1504 """ 1505 variables_reads = [n.variables_read for n in self.nodes if n.contains_require_or_assert()] 1506 variables_read = [item for sublist in variables_reads for item in sublist] 1507 return variable in variables_read 1508 1509 def is_writing(self, variable: "Variable") -> bool: 1510 """ 1511 Check if the function writes the variable 1512 Args: 1513 variable (Variable): 1514 Returns: 1515 bool: True if the variable is written 1516 """ 1517 return variable in self.variables_written 1518 1519 @abstractmethod 1520 def get_summary( 1521 self, 1522 ) -> tuple[str, str, str, list[str], list[str], list[str], list[str], list[str]]: 1523 pass 1524 1525 def is_protected(self) -> bool: 1526 """ 1527 Determine if the function is protected using a check on msg.sender 1528 1529 Consider onlyOwner as a safe modifier. 1530 If the owner functionality is incorrectly implemented, this will lead to incorrectly 1531 classify the function as protected 1532 1533 Otherwise only detects if msg.sender is directly used in a condition 1534 For example, it wont work for: 1535 address a = msg.sender 1536 require(a == owner) 1537 Returns 1538 (bool) 1539 """ 1540 1541 if self._is_protected is None: 1542 if self.is_constructor: 1543 self._is_protected = True 1544 return True 1545 if "onlyOwner" in [m.name for m in self.modifiers]: 1546 self._is_protected = True 1547 return True 1548 conditional_vars = self.all_conditional_solidity_variables_read(include_loop=False) 1549 args_vars = self.all_solidity_variables_used_as_args() 1550 self._is_protected = ( 1551 SolidityVariableComposed("msg.sender") in conditional_vars + args_vars 1552 ) 1553 return self._is_protected 1554 1555 @property 1556 def is_reentrant(self) -> bool: 1557 """ 1558 Determine if the function can be re-entered 1559 """ 1560 reentrancy_modifier = "nonReentrant" 1561 1562 if self.function_language == FunctionLanguage.Vyper: 1563 reentrancy_modifier = "nonreentrant(lock)" 1564 1565 # TODO: compare with hash of known nonReentrant modifier instead of the name 1566 if reentrancy_modifier in [m.name for m in self.modifiers]: 1567 return False 1568 1569 if self.visibility in ["public", "external"]: 1570 return True 1571 1572 # If it's an internal function, check if all its entry points have the nonReentrant modifier 1573 all_entry_points = [ 1574 f for f in self.all_reachable_from_functions if f.visibility in ["public", "external"] 1575 ] 1576 if not all_entry_points: 1577 return True 1578 return not all( 1579 reentrancy_modifier in [m.name for m in f.modifiers] for f in all_entry_points 1580 ) 1581 1582 # endregion 1583 ################################################################################### 1584 ################################################################################### 1585 # region Analyses 1586 ################################################################################### 1587 ################################################################################### 1588 1589 def _analyze_read_write(self) -> None: 1590 """Compute variables read/written/...""" 1591 write_var = [x.variables_written_as_expression for x in self.nodes] 1592 write_var = [x for x in write_var if x] 1593 write_var = [item for sublist in write_var for item in sublist] 1594 write_var = list(set(write_var)) 1595 # Remove duplicate if they share the same string representation 1596 write_var = [ 1597 next(obj) 1598 for i, obj in groupby(sorted(write_var, key=lambda x: str(x)), lambda x: str(x)) 1599 ] 1600 self._expression_vars_written = write_var 1601 1602 write_var = [x.variables_written for x in self.nodes] 1603 write_var = [x for x in write_var if x] 1604 write_var = [item for sublist in write_var for item in sublist] 1605 write_var = list(set(write_var)) 1606 # Remove duplicate if they share the same string representation 1607 write_var = [ 1608 next(obj) 1609 for i, obj in groupby(sorted(write_var, key=lambda x: str(x)), lambda x: str(x)) 1610 ] 1611 self._vars_written = write_var 1612 1613 read_var = [x.variables_read_as_expression for x in self.nodes] 1614 read_var = [x for x in read_var if x] 1615 read_var = [item for sublist in read_var for item in sublist] 1616 # Remove duplicate if they share the same string representation 1617 read_var = [ 1618 next(obj) 1619 for i, obj in groupby(sorted(read_var, key=lambda x: str(x)), lambda x: str(x)) 1620 ] 1621 self._expression_vars_read = read_var 1622 1623 read_var = [x.variables_read for x in self.nodes] 1624 read_var = [x for x in read_var if x] 1625 read_var = [item for sublist in read_var for item in sublist] 1626 # Remove duplicate if they share the same string representation 1627 read_var = [ 1628 next(obj) 1629 for i, obj in groupby(sorted(read_var, key=lambda x: str(x)), lambda x: str(x)) 1630 ] 1631 self._vars_read = read_var 1632 1633 self._state_vars_written = [ 1634 x for x in self.variables_written if isinstance(x, StateVariable) 1635 ] 1636 self._state_vars_read = [x for x in self.variables_read if isinstance(x, StateVariable)] 1637 self._solidity_vars_read = [ 1638 x for x in self.variables_read if isinstance(x, SolidityVariable) 1639 ] 1640 1641 self._vars_read_or_written = self._vars_written + self._vars_read 1642 1643 slithir_variables = [x.slithir_variables for x in self.nodes] 1644 slithir_variables = [x for x in slithir_variables if x] 1645 self._slithir_variables = [item for sublist in slithir_variables for item in sublist] 1646 1647 def _analyze_calls(self) -> None: 1648 calls = [x.calls_as_expression for x in self.nodes] 1649 calls = [x for x in calls if x] 1650 calls = [item for sublist in calls for item in sublist] 1651 self._expression_calls = list(set(calls)) 1652 1653 internal_calls = [x.internal_calls for x in self.nodes] 1654 internal_calls = [x for x in internal_calls if x] 1655 internal_calls = [item for sublist in internal_calls for item in sublist] 1656 self._internal_calls = list(set(internal_calls)) 1657 1658 self._solidity_calls = [ 1659 ir for ir in internal_calls if isinstance(ir.function, SolidityFunction) 1660 ] 1661 1662 low_level_calls = [x.low_level_calls for x in self.nodes] 1663 low_level_calls = [x for x in low_level_calls if x] 1664 low_level_calls = [item for sublist in low_level_calls for item in sublist] 1665 self._low_level_calls = list(set(low_level_calls)) 1666 1667 high_level_calls = [x.high_level_calls for x in self.nodes] 1668 high_level_calls = [x for x in high_level_calls if x] 1669 high_level_calls = [item for sublist in high_level_calls for item in sublist] 1670 self._high_level_calls = list(set(high_level_calls)) 1671 1672 library_calls = [x.library_calls for x in self.nodes] 1673 library_calls = [x for x in library_calls if x] 1674 library_calls = [item for sublist in library_calls for item in sublist] 1675 self._library_calls = list(set(library_calls)) 1676 1677 external_calls_as_expressions = [x.external_calls_as_expressions for x in self.nodes] 1678 external_calls_as_expressions = [x for x in external_calls_as_expressions if x] 1679 external_calls_as_expressions = [ 1680 item for sublist in external_calls_as_expressions for item in sublist 1681 ] 1682 self._external_calls_as_expressions = list(set(external_calls_as_expressions)) 1683 1684 # endregion 1685 ################################################################################### 1686 ################################################################################### 1687 # region Nodes 1688 ################################################################################### 1689 ################################################################################### 1690 1691 def new_node( 1692 self, node_type: "NodeType", src: str | dict, scope: Union[Scope, "Function"] 1693 ) -> "Node": 1694 from slither.core.cfg.node import Node 1695 1696 node = Node(node_type, self._counter_nodes, scope, self.file_scope) 1697 node.set_offset(src, self.compilation_unit) 1698 self._counter_nodes += 1 1699 node.set_function(self) 1700 self._nodes.append(node) 1701 1702 return node 1703 1704 # endregion 1705 ################################################################################### 1706 ################################################################################### 1707 # region SlithIr and SSA 1708 ################################################################################### 1709 ################################################################################### 1710 1711 def _get_last_ssa_variable_instances( 1712 self, target_state: bool, target_local: bool 1713 ) -> dict[str, set["SlithIRVariable"]]: 1714 from slither.slithir.variables import ReferenceVariable 1715 from slither.slithir.operations import OperationWithLValue 1716 from slither.core.cfg.node import NodeType 1717 1718 if not self.is_implemented: 1719 return {} 1720 1721 if self._entry_point is None: 1722 return {} 1723 # node, values 1724 to_explore: list[tuple[Node, dict]] = [(self._entry_point, {})] 1725 # node -> values 1726 explored: dict = {} 1727 # name -> instances 1728 ret: dict = {} 1729 1730 while to_explore: 1731 node, values = to_explore[0] 1732 to_explore = to_explore[1::] 1733 1734 if node.type != NodeType.ENTRYPOINT: 1735 for ir_ssa in node.irs_ssa: 1736 if isinstance(ir_ssa, OperationWithLValue): 1737 lvalue = ir_ssa.lvalue 1738 if isinstance(lvalue, ReferenceVariable): 1739 lvalue = lvalue.points_to_origin 1740 if isinstance(lvalue, StateVariable) and target_state: 1741 values[lvalue.canonical_name] = {lvalue} 1742 if isinstance(lvalue, LocalVariable) and target_local: 1743 values[lvalue.canonical_name] = {lvalue} 1744 1745 # Check for fixpoint 1746 if node in explored: 1747 if values == explored[node]: 1748 continue 1749 for k, instances in values.items(): 1750 if k not in explored[node]: 1751 explored[node][k] = set() 1752 explored[node][k] |= instances 1753 values = explored[node] 1754 else: 1755 explored[node] = values 1756 1757 # Return condition 1758 if node.will_return: 1759 for name, instances in values.items(): 1760 if name not in ret: 1761 ret[name] = set() 1762 ret[name] |= instances 1763 1764 for son in node.sons: 1765 to_explore.append((son, dict(values))) 1766 1767 return ret 1768 1769 def get_last_ssa_state_variables_instances( 1770 self, 1771 ) -> dict[str, set["SlithIRVariable"]]: 1772 return self._get_last_ssa_variable_instances(target_state=True, target_local=False) 1773 1774 def get_last_ssa_local_variables_instances( 1775 self, 1776 ) -> dict[str, set["SlithIRVariable"]]: 1777 return self._get_last_ssa_variable_instances(target_state=False, target_local=True) 1778 1779 @staticmethod 1780 def _unchange_phi(ir: "Operation") -> bool: 1781 from slither.slithir.operations import Phi, PhiCallback 1782 1783 if not isinstance(ir, (Phi, PhiCallback)) or len(ir.rvalues) > 1: 1784 return False 1785 if not ir.rvalues: 1786 return True 1787 return ir.rvalues[0] == ir.lvalue 1788 1789 def _fix_phi_entry( 1790 self, 1791 node: "Node", 1792 last_state_variables_instances: dict[str, list["StateVariable"]], 1793 initial_state_variables_instances: dict[str, "StateVariable"], 1794 ) -> None: 1795 from slither.slithir.variables import Constant, StateIRVariable, LocalIRVariable 1796 1797 for ir in node.irs_ssa: 1798 if isinstance(ir.lvalue, StateIRVariable): 1799 additional = [initial_state_variables_instances[ir.lvalue.canonical_name]] 1800 additional += last_state_variables_instances[ir.lvalue.canonical_name] 1801 ir.rvalues = list(set(additional + ir.rvalues)) 1802 # function parameter that are storage pointer 1803 else: 1804 # find index of the parameter 1805 idx = self.parameters.index(ir.lvalue.non_ssa_version) 1806 # find non ssa version of that index 1807 additional = [n.ir.arguments[idx] for n in self.reachable_from_nodes] 1808 additional = unroll(additional) 1809 additional = [a for a in additional if not isinstance(a, Constant)] 1810 ir.rvalues = list(set(additional + ir.rvalues)) 1811 1812 if isinstance(ir.lvalue, LocalIRVariable) and ir.lvalue.is_storage: 1813 # Update the refers_to to point to the phi rvalues 1814 # This basically means that the local variable is a storage that point to any 1815 # state variable that the storage pointer alias analysis found 1816 ir.lvalue.refers_to = [ 1817 rvalue for rvalue in ir.rvalues if isinstance(rvalue, StateIRVariable) 1818 ] 1819 1820 def fix_phi( 1821 self, 1822 last_state_variables_instances: dict[str, list["StateVariable"]], 1823 initial_state_variables_instances: dict[str, "StateVariable"], 1824 ) -> None: 1825 from slither.slithir.operations import InternalCall, PhiCallback, Phi 1826 from slither.slithir.variables import StateIRVariable, LocalIRVariable 1827 1828 for node in self.nodes: 1829 if node == self.entry_point: 1830 self._fix_phi_entry( 1831 node, last_state_variables_instances, initial_state_variables_instances 1832 ) 1833 for ir in node.irs_ssa: 1834 if isinstance(ir, PhiCallback): 1835 callee_ir = ir.callee_ir 1836 if isinstance(callee_ir, InternalCall): 1837 last_ssa = callee_ir.function.get_last_ssa_state_variables_instances() 1838 if ir.lvalue.canonical_name in last_ssa: 1839 ir.rvalues = list(last_ssa[ir.lvalue.canonical_name]) 1840 else: 1841 ir.rvalues = [ir.lvalue] 1842 else: 1843 additional = last_state_variables_instances[ir.lvalue.canonical_name] 1844 ir.rvalues = list(set(additional + ir.rvalues)) 1845 1846 # Propage storage ref information if it does not exist 1847 # This can happen if the refers_to variable was discovered through the phi operator on function parameter 1848 # aka you have storage pointer as function parameter 1849 # instead of having a storage pointer for which the aliases belong to the function body 1850 if ( 1851 isinstance(ir, Phi) 1852 and isinstance(ir.lvalue, LocalIRVariable) 1853 and ir.lvalue.is_storage 1854 and not ir.lvalue.refers_to 1855 ): 1856 refers_to = [] 1857 for candidate in ir.rvalues: 1858 if isinstance(candidate, StateIRVariable): 1859 refers_to.append(candidate) 1860 if isinstance(candidate, LocalIRVariable) and candidate.is_storage: 1861 refers_to += candidate.refers_to 1862 1863 ir.lvalue.refers_to = refers_to 1864 1865 node.irs_ssa = [ir for ir in node.irs_ssa if not self._unchange_phi(ir)] 1866 1867 def generate_slithir_and_analyze(self) -> None: 1868 for node in self.nodes: 1869 node.slithir_generation() 1870 1871 self._analyze_read_write() 1872 self._analyze_calls() 1873 1874 @abstractmethod 1875 def generate_slithir_ssa(self, all_ssa_state_variables_instances): 1876 pass 1877 1878 def update_read_write_using_ssa(self) -> None: 1879 for node in self.nodes: 1880 node.update_read_write_using_ssa() 1881 self._analyze_read_write() 1882 1883 ################################################################################### 1884 ################################################################################### 1885 # region Built in definitions 1886 ################################################################################### 1887 ################################################################################### 1888 1889 def __str__(self) -> str: 1890 return self.name 1891 1892 # endregion
Function class
240 @property 241 def name(self) -> str: 242 """ 243 str: function name 244 """ 245 if self._name == "" and self._function_type == FunctionType.CONSTRUCTOR: 246 return "constructor" 247 if self._name == "" and self._function_type == FunctionType.FALLBACK: 248 return "fallback" 249 if self._function_type == FunctionType.RECEIVE: 250 return "receive" 251 if self._function_type == FunctionType.CONSTRUCTOR_VARIABLES: 252 return "slitherConstructorVariables" 253 if self._function_type == FunctionType.CONSTRUCTOR_CONSTANT_VARIABLES: 254 return "slitherConstructorConstantVariables" 255 return self._name
str: function name
261 @property 262 def internal_scope(self) -> list[str]: 263 """ 264 Return a list of name representing the scope of the function 265 This is used to model nested functions declared in YUL 266 267 :return: 268 """ 269 return self._internal_scope
Return a list of name representing the scope of the function This is used to model nested functions declared in YUL
Returns
275 @property 276 def full_name(self) -> str: 277 """ 278 str: func_name(type1,type2) 279 Return the function signature without the return values 280 The difference between this function and solidity_function is that full_name does not translate the underlying 281 type (ex: structure, contract to address, ...) 282 """ 283 if self._full_name is None: 284 name, parameters, _ = self.signature 285 full_name = ".".join(self._internal_scope + [name]) + "(" + ",".join(parameters) + ")" 286 self._full_name = full_name 287 return self._full_name
str: func_name(type1,type2) Return the function signature without the return values The difference between this function and solidity_function is that full_name does not translate the underlying type (ex: structure, contract to address, ...)
289 @property 290 @abstractmethod 291 def canonical_name(self) -> str: 292 """ 293 str: contract.func_name(type1,type2) 294 Return the function signature without the return values 295 """ 296 return ""
str: contract.func_name(type1,type2) Return the function signature without the return values
306 def can_reenter(self, callstack: list[Union["Function", "Variable"]] | None = None) -> bool: 307 """ 308 Check if the function can re-enter 309 Follow internal calls. 310 Do not consider CREATE as potential re-enter, but check if the 311 destination's constructor can contain a call (recurs. follow nested CREATE) 312 For Solidity > 0.5, filter access to public variables and constant/pure/view 313 For call to this. check if the destination can re-enter 314 Do not consider Send/Transfer as there is not enough gas 315 :param callstack: used internally to check for recursion 316 :return bool: 317 """ 318 from slither.slithir.operations import Call 319 320 if self._can_reenter is None: 321 self._can_reenter = False 322 for ir in self.all_slithir_operations(): 323 if isinstance(ir, Call) and ir.can_reenter(callstack): 324 self._can_reenter = True 325 return True 326 return self._can_reenter
Check if the function can re-enter Follow internal calls. Do not consider CREATE as potential re-enter, but check if the destination's constructor can contain a call (recurs. follow nested CREATE) For Solidity > 0.5, filter access to public variables and constant/pure/view For call to this. check if the destination can re-enter Do not consider Send/Transfer as there is not enough gas
Parameters
- callstack: used internally to check for recursion
Returns
328 def can_send_eth(self) -> bool: 329 """ 330 Check if the function or any internal (not external) functions called by it can send eth 331 :return bool: 332 """ 333 from slither.slithir.operations import Call 334 335 if self._can_send_eth is None: 336 self._can_send_eth = False 337 for ir in self.all_slithir_operations(): 338 if isinstance(ir, Call) and ir.can_send_eth(): 339 self._can_send_eth = True 340 return True 341 return self._can_send_eth
Check if the function or any internal (not external) functions called by it can send eth
Returns
343 @property 344 def is_checked(self) -> bool: 345 """ 346 Return true if the overflow are enabled by default 347 348 349 :return: 350 """ 351 352 return self.compilation_unit.solc_version >= "0.8.0"
Return true if the overflow are enabled by default
Returns
354 @property 355 def id(self) -> str | None: 356 """ 357 Return the reference ID of the function, if available. 358 359 :return: 360 :rtype: 361 """ 362 return self._id
Return the reference ID of the function, if available.
Returns
392 @property 393 def is_constructor(self) -> bool: 394 """ 395 bool: True if the function is the constructor 396 """ 397 return self._function_type == FunctionType.CONSTRUCTOR
bool: True if the function is the constructor
399 @property 400 def is_constructor_variables(self) -> bool: 401 """ 402 bool: True if the function is the constructor of the variables 403 Slither has inbuilt functions to hold the state variables initialization 404 """ 405 return self._function_type in [ 406 FunctionType.CONSTRUCTOR_VARIABLES, 407 FunctionType.CONSTRUCTOR_CONSTANT_VARIABLES, 408 ]
bool: True if the function is the constructor of the variables Slither has inbuilt functions to hold the state variables initialization
410 @property 411 def is_fallback(self) -> bool: 412 """ 413 Determine if the function is the fallback function for the contract 414 Returns 415 (bool) 416 """ 417 return self._function_type == FunctionType.FALLBACK
Determine if the function is the fallback function for the contract Returns (bool)
419 @property 420 def is_receive(self) -> bool: 421 """ 422 Determine if the function is the receive function for the contract 423 Returns 424 (bool) 425 """ 426 return self._function_type == FunctionType.RECEIVE
Determine if the function is the receive function for the contract Returns (bool)
435 @property 436 def payable(self) -> bool: 437 """ 438 bool: True if the function is payable 439 """ 440 return self._payable
bool: True if the function is payable
453 @property 454 def is_virtual(self) -> bool: 455 """ 456 Note for Solidity < 0.6.0 it will always be false 457 bool: True if the function is virtual 458 """ 459 return self._virtual
Note for Solidity < 0.6.0 it will always be false bool: True if the function is virtual
465 @property 466 def is_override(self) -> bool: 467 """ 468 Note for Solidity < 0.6.0 it will always be false 469 bool: True if the function overrides a base function 470 """ 471 return len(self._overrides) > 0
Note for Solidity < 0.6.0 it will always be false bool: True if the function overrides a base function
473 @property 474 def overridden_by(self) -> list["FunctionContract"]: 475 """ 476 List["FunctionContract"]: List of functions in child contracts that override this function 477 This may include distinct instances of the same function due to inheritance 478 """ 479 return self._overridden_by
List["FunctionContract"]: List of functions in child contracts that override this function This may include distinct instances of the same function due to inheritance
481 @property 482 def overrides(self) -> list["FunctionContract"]: 483 """ 484 List["FunctionContract"]: List of functions in parent contracts that this function overrides 485 This may include distinct instances of the same function due to inheritance 486 """ 487 return self._overrides
List["FunctionContract"]: List of functions in parent contracts that this function overrides This may include distinct instances of the same function due to inheritance
496 @property 497 def visibility(self) -> str: 498 """ 499 str: Function visibility 500 """ 501 assert self._visibility is not None 502 return self._visibility
str: Function visibility
511 @property 512 def view(self) -> bool: 513 """ 514 bool: True if the function is declared as view 515 """ 516 return self._view
bool: True if the function is declared as view
522 @property 523 def pure(self) -> bool: 524 """ 525 bool: True if the function is declared as pure 526 """ 527 return self._pure
bool: True if the function is declared as pure
556 @property 557 def is_implemented(self) -> bool: 558 """ 559 bool: True if the function is implemented 560 """ 561 return self._is_implemented
bool: True if the function is implemented
567 @property 568 def is_empty(self) -> bool: 569 """ 570 bool: True if the function is empty, None if the function is an interface 571 """ 572 return self._is_empty
bool: True if the function is empty, None if the function is an interface
585 @property 586 def nodes(self) -> list["Node"]: 587 """ 588 list(Node): List of the nodes 589 """ 590 return list(self._nodes)
list(Node): List of the nodes
596 @property 597 def entry_point(self) -> Optional["Node"]: 598 """ 599 Node: Entry point of the function 600 """ 601 return self._entry_point
Node: Entry point of the function
612 @property 613 def nodes_ordered_dominators(self) -> list["Node"]: 614 # TODO: does not work properly; most likely due to modifier call 615 # This will not work for modifier call that lead to multiple nodes 616 # from slither.core.cfg.node import NodeType 617 if self._nodes_ordered_dominators is None: 618 self._nodes_ordered_dominators = [] 619 if self.entry_point: 620 self._compute_nodes_ordered_dominators(self.entry_point) 621 622 for node in self.nodes: 623 # if node.type == NodeType.OTHER_ENTRYPOINT: 624 if node not in self._nodes_ordered_dominators: 625 self._compute_nodes_ordered_dominators(node) 626 627 return self._nodes_ordered_dominators
644 @property 645 def parameters(self) -> list["LocalVariable"]: 646 """ 647 list(LocalVariable): List of the parameters 648 """ 649 return list(self._parameters)
list(LocalVariable): List of the parameters
654 @property 655 def parameters_ssa(self) -> list["LocalIRVariable"]: 656 """ 657 list(LocalIRVariable): List of the parameters (SSA form) 658 """ 659 return list(self._parameters_ssa)
list(LocalIRVariable): List of the parameters (SSA form)
674 @property 675 def return_type(self) -> list[Type] | None: 676 """ 677 Return the list of return type 678 If no return, return None 679 """ 680 returns = self.returns 681 if returns: 682 return [r.type for r in returns] 683 return None
Return the list of return type If no return, return None
688 @property 689 def type(self) -> list[Type] | None: 690 """ 691 Return the list of return type 692 If no return, return None 693 Alias of return_type 694 """ 695 return self.return_type
Return the list of return type If no return, return None Alias of return_type
697 @property 698 def returns(self) -> list["LocalVariable"]: 699 """ 700 list(LocalVariable): List of the return variables 701 """ 702 return list(self._returns)
list(LocalVariable): List of the return variables
707 @property 708 def returns_ssa(self) -> list["LocalIRVariable"]: 709 """ 710 list(LocalIRVariable): List of the return variables (SSA form) 711 """ 712 return list(self._returns_ssa)
list(LocalIRVariable): List of the return variables (SSA form)
724 @property 725 def modifiers(self) -> list[Union["Contract", "Function"]]: 726 """ 727 list(Modifier): List of the modifiers 728 Can be contract for constructor's calls 729 730 """ 731 return [c.modifier for c in self._modifiers]
list(Modifier): List of the modifiers Can be contract for constructor's calls
736 @property 737 def modifiers_statements(self) -> list[ModifierStatements]: 738 """ 739 list(ModifierCall): List of the modifiers call (include expression and irs) 740 """ 741 return list(self._modifiers)
list(ModifierCall): List of the modifiers call (include expression and irs)
743 @property 744 def explicit_base_constructor_calls(self) -> list["Function"]: 745 """ 746 list(Function): List of the base constructors called explicitly by this presumed constructor definition. 747 748 Base constructors implicitly or explicitly called by the contract definition will not be 749 included. 750 """ 751 # This is a list of contracts internally, so we convert it to a list of constructor functions. 752 return [ 753 c.modifier.constructors_declared 754 for c in self._explicit_base_constructor_calls 755 if c.modifier.constructors_declared 756 ]
list(Function): List of the base constructors called explicitly by this presumed constructor definition.
Base constructors implicitly or explicitly called by the contract definition will not be included.
758 @property 759 def explicit_base_constructor_calls_statements(self) -> list[ModifierStatements]: 760 """ 761 list(ModifierCall): List of the base constructors called explicitly by this presumed constructor definition. 762 763 """ 764 # This is a list of contracts internally, so we convert it to a list of constructor functions. 765 return list(self._explicit_base_constructor_calls)
list(ModifierCall): List of the base constructors called explicitly by this presumed constructor definition.
777 @property 778 def variables(self) -> list[LocalVariable]: 779 """ 780 Return all local variables 781 Include parameters and return values 782 """ 783 return list(self._variables.values())
Return all local variables Include parameters and return values
785 @property 786 def local_variables(self) -> list[LocalVariable]: 787 """ 788 Return all local variables (dont include parameters and return values) 789 """ 790 return list(set(self.variables) - set(self.returns) - set(self.parameters))
Return all local variables (dont include parameters and return values)
796 @property 797 def variables_read(self) -> list["Variable"]: 798 """ 799 list(Variable): Variables read (local/state/solidity) 800 """ 801 return list(self._vars_read)
list(Variable): Variables read (local/state/solidity)
803 @property 804 def variables_written(self) -> list["Variable"]: 805 """ 806 list(Variable): Variables written (local/state/solidity) 807 """ 808 return list(self._vars_written)
list(Variable): Variables written (local/state/solidity)
810 @property 811 def state_variables_read(self) -> list["StateVariable"]: 812 """ 813 list(StateVariable): State variables read 814 """ 815 return list(self._state_vars_read)
list(StateVariable): State variables read
817 @property 818 def solidity_variables_read(self) -> list["SolidityVariable"]: 819 """ 820 list(SolidityVariable): Solidity variables read 821 """ 822 return list(self._solidity_vars_read)
list(SolidityVariable): Solidity variables read
824 @property 825 def state_variables_written(self) -> list["StateVariable"]: 826 """ 827 list(StateVariable): State variables written 828 """ 829 return list(self._state_vars_written)
list(StateVariable): State variables written
831 @property 832 def variables_read_or_written(self) -> list["Variable"]: 833 """ 834 list(Variable): Variables read or written (local/state/solidity) 835 """ 836 return list(self._vars_read_or_written)
list(Variable): Variables read or written (local/state/solidity)
846 @property 847 def slithir_variables(self) -> list["SlithIRVariable"]: 848 """ 849 Temporary and Reference Variables (not SSA form) 850 """ 851 852 return list(self._slithir_variables)
Temporary and Reference Variables (not SSA form)
861 @property 862 def internal_calls(self) -> list["InternalCall"]: 863 """ 864 list(InternalCall): List of IR operations for internal calls 865 """ 866 return list(self._internal_calls)
list(InternalCall): List of IR operations for internal calls
868 @property 869 def solidity_calls(self) -> list["SolidityCall"]: 870 """ 871 list(SolidityCall): List of IR operations for Solidity calls 872 """ 873 return list(self._solidity_calls)
list(SolidityCall): List of IR operations for Solidity calls
875 @property 876 def high_level_calls(self) -> list[tuple["Contract", "HighLevelCall"]]: 877 """ 878 list(Tuple(Contract, "HighLevelCall")): List of call target contract and IR of the high level call 879 A variable is called in case of call to a public state variable 880 Include library calls 881 """ 882 return list(self._high_level_calls)
list(Tuple(Contract, "HighLevelCall")): List of call target contract and IR of the high level call A variable is called in case of call to a public state variable Include library calls
884 @property 885 def library_calls(self) -> list["LibraryCall"]: 886 """ 887 list(LibraryCall): List of IR operations for library calls 888 """ 889 return list(self._library_calls)
list(LibraryCall): List of IR operations for library calls
891 @property 892 def low_level_calls(self) -> list["LowLevelCall"]: 893 """ 894 list(LowLevelCall): List of IR operations for low level calls 895 A low level call is defined by 896 - the variable called 897 - the name of the function (call/delegatecall/callcode) 898 """ 899 return list(self._low_level_calls)
list(LowLevelCall): List of IR operations for low level calls A low level call is defined by
- the variable called
- the name of the function (call/delegatecall/callcode)
901 @property 902 def external_calls_as_expressions(self) -> list["Expression"]: 903 """ 904 list(ExpressionCall): List of message calls (that creates a transaction) 905 """ 906 return list(self._external_calls_as_expressions)
list(ExpressionCall): List of message calls (that creates a transaction)
919 @property 920 def expressions(self) -> list["Expression"]: 921 """ 922 list(Expression): List of the expressions 923 """ 924 if self._expressions is None: 925 expressionss = [n.expression for n in self.nodes] 926 expressions = [e for e in expressionss if e] 927 self._expressions = expressions 928 return self._expressions
list(Expression): List of the expressions
930 @property 931 def return_values(self) -> list["SlithIRVariable"]: 932 """ 933 list(Return Values): List of the return values 934 """ 935 from slither.core.cfg.node import NodeType 936 from slither.slithir.operations import Return 937 from slither.slithir.variables import Constant 938 939 if self._return_values is None: 940 return_values = [] 941 returns = [n for n in self.nodes if n.type == NodeType.RETURN] 942 [ 943 return_values.extend(ir.values) 944 for node in returns 945 for ir in node.irs 946 if isinstance(ir, Return) 947 ] 948 self._return_values = list({x for x in return_values if not isinstance(x, Constant)}) 949 return self._return_values
list(Return Values): List of the return values
951 @property 952 def return_values_ssa(self) -> list["SlithIRVariable"]: 953 """ 954 list(Return Values in SSA form): List of the return values in ssa form 955 """ 956 from slither.core.cfg.node import NodeType 957 from slither.slithir.operations import Return 958 from slither.slithir.variables import Constant 959 960 if self._return_values_ssa is None: 961 return_values_ssa = [] 962 returns = [n for n in self.nodes if n.type == NodeType.RETURN] 963 [ 964 return_values_ssa.extend(ir.values) 965 for node in returns 966 for ir in node.irs_ssa 967 if isinstance(ir, Return) 968 ] 969 self._return_values_ssa = list( 970 {x for x in return_values_ssa if not isinstance(x, Constant)} 971 ) 972 return self._return_values_ssa
list(Return Values in SSA form): List of the return values in ssa form
981 @property 982 def slithir_operations(self) -> list["Operation"]: 983 """ 984 list(Operation): List of the slithir operations 985 """ 986 if self._slithir_operations is None: 987 operationss = [n.irs for n in self.nodes] 988 operations = [item for sublist in operationss for item in sublist if item] 989 self._slithir_operations = operations 990 return self._slithir_operations
list(Operation): List of the slithir operations
992 @property 993 def slithir_ssa_operations(self) -> list["Operation"]: 994 """ 995 list(Operation): List of the slithir operations (SSA) 996 """ 997 if self._slithir_ssa_operations is None: 998 operationss = [n.irs_ssa for n in self.nodes] 999 operations = [item for sublist in operationss for item in sublist if item] 1000 self._slithir_ssa_operations = operations 1001 return self._slithir_ssa_operations
list(Operation): List of the slithir operations (SSA)
1010 @property 1011 def solidity_signature(self) -> str: 1012 """ 1013 Return a signature following the Solidity Standard 1014 Contract and converted into address 1015 1016 It might still keep internal types (ex: structure name) for internal functions. 1017 The reason is that internal functions allows recursive structure definition, which 1018 can't be converted following the Solidity stand ard 1019 1020 :return: the solidity signature 1021 """ 1022 if self._solidity_signature is None: 1023 parameters = [ 1024 convert_type_for_solidity_signature_to_string(x.type) for x in self.parameters 1025 ] 1026 self._solidity_signature = self.name + "(" + ",".join(parameters) + ")" 1027 return self._solidity_signature
Return a signature following the Solidity Standard Contract and converted into address
It might still keep internal types (ex: structure name) for internal functions. The reason is that internal functions allows recursive structure definition, which can't be converted following the Solidity stand ard
Returns
the solidity signature
1029 @property 1030 def signature(self) -> tuple[str, list[str], list[str]]: 1031 """ 1032 (str, list(str), list(str)): Function signature as 1033 (name, list parameters type, list return values type) 1034 """ 1035 # FIXME memoizing this function is not working properly for vyper 1036 # if self._signature is None: 1037 return ( 1038 self.name, 1039 [str(x.type) for x in self.parameters], 1040 [str(x.type) for x in self.returns], 1041 ) 1042 # self._signature = signature 1043 # return self._signature
(str, list(str), list(str)): Function signature as (name, list parameters type, list return values type)
1045 @property 1046 def signature_str(self) -> str: 1047 """ 1048 str: func_name(type1,type2) returns (type3) 1049 Return the function signature as a str (contains the return values) 1050 """ 1051 if self._signature_str is None: 1052 name, parameters, returnVars = self.signature 1053 self._signature_str = ( 1054 name + "(" + ",".join(parameters) + ") returns(" + ",".join(returnVars) + ")" 1055 ) 1056 return self._signature_str
str: func_name(type1,type2) returns (type3) Return the function signature as a str (contains the return values)
1077 @property 1078 def reachable_from_nodes(self) -> set[ReacheableNode]: 1079 """ 1080 Return 1081 ReacheableNode 1082 """ 1083 return self._reachable_from_nodes
Return ReacheableNode
1089 @property 1090 def all_reachable_from_functions(self) -> set["Function"]: 1091 """ 1092 Give the recursive version of reachable_from_functions (all the functions that lead to call self in the CFG) 1093 """ 1094 if self._all_reachable_from_functions is None: 1095 functions: set[Function] = set() 1096 1097 new_functions = self.reachable_from_functions 1098 # iterate until we have are finding new functions 1099 while new_functions and not new_functions.issubset(functions): 1100 functions = functions.union(new_functions) 1101 # Use a temporary set, because we iterate over new_functions 1102 new_functionss: set[Function] = set() 1103 for f in new_functions: 1104 new_functionss = new_functionss.union(f.reachable_from_functions) 1105 new_functions = new_functionss - functions 1106 1107 self._all_reachable_from_functions = functions 1108 return self._all_reachable_from_functions
Give the recursive version of reachable_from_functions (all the functions that lead to call self in the CFG)
1156 def all_variables_read(self) -> list["Variable"]: 1157 """recursive version of variables_read""" 1158 if self._all_variables_read is None: 1159 self._all_variables_read = self._explore_functions(lambda x: x.variables_read) 1160 return self._all_variables_read
recursive version of variables_read
1162 def all_variables_written(self) -> list["Variable"]: 1163 """recursive version of variables_written""" 1164 if self._all_variables_written is None: 1165 self._all_variables_written = self._explore_functions(lambda x: x.variables_written) 1166 return self._all_variables_written
recursive version of variables_written
1168 def all_state_variables_read(self) -> list["StateVariable"]: 1169 """recursive version of variables_read""" 1170 if self._all_state_variables_read is None: 1171 self._all_state_variables_read = self._explore_functions( 1172 lambda x: x.state_variables_read 1173 ) 1174 return self._all_state_variables_read
recursive version of variables_read
1176 def all_solidity_variables_read(self) -> list[SolidityVariable]: 1177 """recursive version of solidity_read""" 1178 if self._all_solidity_variables_read is None: 1179 self._all_solidity_variables_read = self._explore_functions( 1180 lambda x: x.solidity_variables_read 1181 ) 1182 return self._all_solidity_variables_read
recursive version of solidity_read
1184 def all_slithir_variables(self) -> list["SlithIRVariable"]: 1185 """recursive version of slithir_variables""" 1186 if self._all_slithir_variables is None: 1187 self._all_slithir_variables = self._explore_functions(lambda x: x.slithir_variables) 1188 return self._all_slithir_variables
recursive version of slithir_variables
1190 def all_nodes(self) -> list["Node"]: 1191 """recursive version of nodes""" 1192 if self._all_nodes is None: 1193 self._all_nodes = self._explore_functions(lambda x: x.nodes) 1194 return self._all_nodes
recursive version of nodes
1196 def all_expressions(self) -> list["Expression"]: 1197 """recursive version of variables_read""" 1198 if self._all_expressions is None: 1199 self._all_expressions = self._explore_functions(lambda x: x.expressions) 1200 return self._all_expressions
recursive version of variables_read
1207 def all_state_variables_written(self) -> list[StateVariable]: 1208 """recursive version of variables_written""" 1209 if self._all_state_variables_written is None: 1210 self._all_state_variables_written = self._explore_functions( 1211 lambda x: x.state_variables_written 1212 ) 1213 return self._all_state_variables_written
recursive version of variables_written
1215 def all_internal_calls(self) -> list["InternalCall"]: 1216 """recursive version of internal_calls""" 1217 if self._all_internals_calls is None: 1218 self._all_internals_calls = self._explore_functions(lambda x: x.internal_calls) 1219 return self._all_internals_calls
recursive version of internal_calls
1221 def all_low_level_calls(self) -> list["LowLevelCall"]: 1222 """recursive version of low_level calls""" 1223 if self._all_low_level_calls is None: 1224 self._all_low_level_calls = self._explore_functions(lambda x: x.low_level_calls) 1225 return self._all_low_level_calls
recursive version of low_level calls
1227 def all_high_level_calls(self) -> list[tuple["Contract", "HighLevelCall"]]: 1228 """recursive version of high_level calls""" 1229 if self._all_high_level_calls is None: 1230 self._all_high_level_calls = self._explore_functions(lambda x: x.high_level_calls) 1231 return self._all_high_level_calls
recursive version of high_level calls
1233 def all_library_calls(self) -> list["LibraryCall"]: 1234 """recursive version of library calls""" 1235 if self._all_library_calls is None: 1236 self._all_library_calls = self._explore_functions(lambda x: x.library_calls) 1237 return self._all_library_calls
recursive version of library calls
1239 def all_solidity_calls(self) -> list["SolidityCall"]: 1240 """recursive version of solidity calls""" 1241 if self._all_solidity_calls is None: 1242 self._all_solidity_calls = self._explore_functions(lambda x: x.solidity_calls) 1243 return self._all_solidity_calls
recursive version of solidity calls
1250 def all_conditional_state_variables_read(self, include_loop=True) -> list["StateVariable"]: 1251 """ 1252 Return the state variable used in a condition 1253 1254 Over approximate and also return index access 1255 It won't work if the variable is assigned to a temp variable 1256 """ 1257 if include_loop: 1258 if self._all_conditional_state_variables_read_with_loop is None: 1259 self._all_conditional_state_variables_read_with_loop = self._explore_functions( 1260 lambda x: self._explore_func_cond_read(x, include_loop) 1261 ) 1262 return self._all_conditional_state_variables_read_with_loop 1263 if self._all_conditional_state_variables_read is None: 1264 self._all_conditional_state_variables_read = self._explore_functions( 1265 lambda x: self._explore_func_cond_read(x, include_loop) 1266 ) 1267 return self._all_conditional_state_variables_read
Return the state variable used in a condition
Over approximate and also return index access It won't work if the variable is assigned to a temp variable
1288 def all_conditional_solidity_variables_read( 1289 self, include_loop: bool = True 1290 ) -> list[SolidityVariable]: 1291 """ 1292 Return the Solidity variables directly used in a condition 1293 1294 Use of the IR to filter index access 1295 Assumption: the solidity vars are used directly in the conditional node 1296 It won't work if the variable is assigned to a temp variable 1297 """ 1298 if include_loop: 1299 if self._all_conditional_solidity_variables_read_with_loop is None: 1300 self._all_conditional_solidity_variables_read_with_loop = self._explore_functions( 1301 lambda x: self._explore_func_conditional( 1302 x, self._solidity_variable_in_binary, include_loop 1303 ) 1304 ) 1305 return self._all_conditional_solidity_variables_read_with_loop 1306 1307 if self._all_conditional_solidity_variables_read is None: 1308 self._all_conditional_solidity_variables_read = self._explore_functions( 1309 lambda x: self._explore_func_conditional( 1310 x, self._solidity_variable_in_binary, include_loop 1311 ) 1312 ) 1313 return self._all_conditional_solidity_variables_read
Return the Solidity variables directly used in a condition
Use of the IR to filter index access Assumption: the solidity vars are used directly in the conditional node It won't work if the variable is assigned to a temp variable
1332 def all_solidity_variables_used_as_args(self) -> list[SolidityVariable]: 1333 """ 1334 Return the Solidity variables directly used in a call 1335 1336 Use of the IR to filter index access 1337 Used to catch check(msg.sender) 1338 """ 1339 if self._all_solidity_variables_used_as_args is None: 1340 self._all_solidity_variables_used_as_args = self._explore_functions( 1341 lambda x: self._explore_func_nodes(x, self._solidity_variable_in_internal_calls) 1342 ) 1343 return self._all_solidity_variables_used_as_args
Return the Solidity variables directly used in a call
Use of the IR to filter index access Used to catch check(msg.sender)
1352 def apply_visitor(self, Visitor: Callable) -> list: 1353 """ 1354 Apply a visitor to all the function expressions 1355 Args: 1356 Visitor: slither.visitors 1357 Returns 1358 list(): results of the visit 1359 """ 1360 expressions = self.expressions 1361 v = [Visitor(e).result() for e in expressions] 1362 return [item for sublist in v for item in sublist]
Apply a visitor to all the function expressions Args: Visitor: slither.visitors Returns list(): results of the visit
1371 def get_local_variable_from_name(self, variable_name: str) -> LocalVariable | None: 1372 """ 1373 Return a local variable from a name 1374 1375 Args: 1376 variable_name (str): name of the variable 1377 Returns: 1378 LocalVariable 1379 """ 1380 return next((v for v in self.variables if v.name == variable_name), None)
Return a local variable from a name
Args: variable_name (str): name of the variable Returns: LocalVariable
1389 def cfg_to_dot(self, filename: str): 1390 """ 1391 Export the function to a dot file 1392 Args: 1393 filename (str) 1394 """ 1395 with open(filename, "w", encoding="utf8") as f: 1396 f.write("digraph{\n") 1397 for node in self.nodes: 1398 f.write(f'{node.node_id}[label="{node!s}"];\n') 1399 for son in node.sons: 1400 f.write(f"{node.node_id}->{son.node_id};\n") 1401 1402 f.write("}\n")
Export the function to a dot file Args: filename (str)
1404 def dominator_tree_to_dot(self, filename: str): 1405 """ 1406 Export the dominator tree of the function to a dot file 1407 Args: 1408 filename (str) 1409 """ 1410 1411 def description(node): 1412 desc = f"{node}\n" 1413 desc += f"id: {node.node_id}" 1414 if node.dominance_frontier: 1415 desc += f"\ndominance frontier: {[n.node_id for n in node.dominance_frontier]}" 1416 return desc 1417 1418 with open(filename, "w", encoding="utf8") as f: 1419 f.write("digraph{\n") 1420 for node in self.nodes: 1421 f.write(f'{node.node_id}[label="{description(node)}"];\n') 1422 if node.immediate_dominator: 1423 f.write(f"{node.immediate_dominator.node_id}->{node.node_id};\n") 1424 1425 f.write("}\n")
Export the dominator tree of the function to a dot file Args: filename (str)
1427 def slithir_cfg_to_dot(self, filename: str): 1428 """ 1429 Export the CFG to a DOT file. The nodes includes the Solidity expressions and the IRs 1430 :param filename: 1431 :return: 1432 """ 1433 content = self.slithir_cfg_to_dot_str() 1434 with open(filename, "w", encoding="utf8") as f: 1435 f.write(content)
Export the CFG to a DOT file. The nodes includes the Solidity expressions and the IRs
Parameters
- filename:
Returns
1437 def slithir_cfg_to_dot_str(self, skip_expressions: bool = False) -> str: 1438 """ 1439 Export the CFG to a DOT format. The nodes includes the Solidity expressions and the IRs 1440 :return: the DOT content 1441 :rtype: str 1442 """ 1443 from slither.core.cfg.node import NodeType 1444 1445 content = "" 1446 content += "digraph{\n" 1447 for node in self.nodes: 1448 label = f"Node Type: {node.type.value} {node.node_id}\n" 1449 if node.expression and not skip_expressions: 1450 label += f"\nEXPRESSION:\n{node.expression}\n" 1451 if node.irs and not skip_expressions: 1452 label += "\nIRs:\n" + "\n".join([str(ir) for ir in node.irs]) 1453 content += f'{node.node_id}[label="{label}"];\n' 1454 if node.type in [NodeType.IF, NodeType.IFLOOP]: 1455 true_node = node.son_true 1456 if true_node: 1457 content += f'{node.node_id}->{true_node.node_id}[label="True"];\n' 1458 false_node = node.son_false 1459 if false_node: 1460 content += f'{node.node_id}->{false_node.node_id}[label="False"];\n' 1461 else: 1462 for son in node.sons: 1463 content += f"{node.node_id}->{son.node_id};\n" 1464 1465 content += "}\n" 1466 return content
Export the CFG to a DOT format. The nodes includes the Solidity expressions and the IRs
Returns
the DOT content
1475 def is_reading(self, variable: "Variable") -> bool: 1476 """ 1477 Check if the function reads the variable 1478 Args: 1479 variable (Variable): 1480 Returns: 1481 bool: True if the variable is read 1482 """ 1483 return variable in self.variables_read
Check if the function reads the variable Args: variable (Variable): Returns: bool: True if the variable is read
1485 def is_reading_in_conditional_node(self, variable: "Variable") -> bool: 1486 """ 1487 Check if the function reads the variable in a IF node 1488 Args: 1489 variable (Variable): 1490 Returns: 1491 bool: True if the variable is read 1492 """ 1493 variables_reads = [n.variables_read for n in self.nodes if n.contains_if()] 1494 variables_read = [item for sublist in variables_reads for item in sublist] 1495 return variable in variables_read
Check if the function reads the variable in a IF node Args: variable (Variable): Returns: bool: True if the variable is read
1497 def is_reading_in_require_or_assert(self, variable: "Variable") -> bool: 1498 """ 1499 Check if the function reads the variable in an require or assert 1500 Args: 1501 variable (Variable): 1502 Returns: 1503 bool: True if the variable is read 1504 """ 1505 variables_reads = [n.variables_read for n in self.nodes if n.contains_require_or_assert()] 1506 variables_read = [item for sublist in variables_reads for item in sublist] 1507 return variable in variables_read
Check if the function reads the variable in an require or assert Args: variable (Variable): Returns: bool: True if the variable is read
1509 def is_writing(self, variable: "Variable") -> bool: 1510 """ 1511 Check if the function writes the variable 1512 Args: 1513 variable (Variable): 1514 Returns: 1515 bool: True if the variable is written 1516 """ 1517 return variable in self.variables_written
Check if the function writes the variable Args: variable (Variable): Returns: bool: True if the variable is written
1525 def is_protected(self) -> bool: 1526 """ 1527 Determine if the function is protected using a check on msg.sender 1528 1529 Consider onlyOwner as a safe modifier. 1530 If the owner functionality is incorrectly implemented, this will lead to incorrectly 1531 classify the function as protected 1532 1533 Otherwise only detects if msg.sender is directly used in a condition 1534 For example, it wont work for: 1535 address a = msg.sender 1536 require(a == owner) 1537 Returns 1538 (bool) 1539 """ 1540 1541 if self._is_protected is None: 1542 if self.is_constructor: 1543 self._is_protected = True 1544 return True 1545 if "onlyOwner" in [m.name for m in self.modifiers]: 1546 self._is_protected = True 1547 return True 1548 conditional_vars = self.all_conditional_solidity_variables_read(include_loop=False) 1549 args_vars = self.all_solidity_variables_used_as_args() 1550 self._is_protected = ( 1551 SolidityVariableComposed("msg.sender") in conditional_vars + args_vars 1552 ) 1553 return self._is_protected
Determine if the function is protected using a check on msg.sender
Consider onlyOwner as a safe modifier.
If the owner functionality is incorrectly implemented, this will lead to incorrectly
classify the function as protected
Otherwise only detects if msg.sender is directly used in a condition
For example, it wont work for:
address a = msg.sender
require(a == owner)
Returns (bool)
1555 @property 1556 def is_reentrant(self) -> bool: 1557 """ 1558 Determine if the function can be re-entered 1559 """ 1560 reentrancy_modifier = "nonReentrant" 1561 1562 if self.function_language == FunctionLanguage.Vyper: 1563 reentrancy_modifier = "nonreentrant(lock)" 1564 1565 # TODO: compare with hash of known nonReentrant modifier instead of the name 1566 if reentrancy_modifier in [m.name for m in self.modifiers]: 1567 return False 1568 1569 if self.visibility in ["public", "external"]: 1570 return True 1571 1572 # If it's an internal function, check if all its entry points have the nonReentrant modifier 1573 all_entry_points = [ 1574 f for f in self.all_reachable_from_functions if f.visibility in ["public", "external"] 1575 ] 1576 if not all_entry_points: 1577 return True 1578 return not all( 1579 reentrancy_modifier in [m.name for m in f.modifiers] for f in all_entry_points 1580 )
Determine if the function can be re-entered
1691 def new_node( 1692 self, node_type: "NodeType", src: str | dict, scope: Union[Scope, "Function"] 1693 ) -> "Node": 1694 from slither.core.cfg.node import Node 1695 1696 node = Node(node_type, self._counter_nodes, scope, self.file_scope) 1697 node.set_offset(src, self.compilation_unit) 1698 self._counter_nodes += 1 1699 node.set_function(self) 1700 self._nodes.append(node) 1701 1702 return node
1820 def fix_phi( 1821 self, 1822 last_state_variables_instances: dict[str, list["StateVariable"]], 1823 initial_state_variables_instances: dict[str, "StateVariable"], 1824 ) -> None: 1825 from slither.slithir.operations import InternalCall, PhiCallback, Phi 1826 from slither.slithir.variables import StateIRVariable, LocalIRVariable 1827 1828 for node in self.nodes: 1829 if node == self.entry_point: 1830 self._fix_phi_entry( 1831 node, last_state_variables_instances, initial_state_variables_instances 1832 ) 1833 for ir in node.irs_ssa: 1834 if isinstance(ir, PhiCallback): 1835 callee_ir = ir.callee_ir 1836 if isinstance(callee_ir, InternalCall): 1837 last_ssa = callee_ir.function.get_last_ssa_state_variables_instances() 1838 if ir.lvalue.canonical_name in last_ssa: 1839 ir.rvalues = list(last_ssa[ir.lvalue.canonical_name]) 1840 else: 1841 ir.rvalues = [ir.lvalue] 1842 else: 1843 additional = last_state_variables_instances[ir.lvalue.canonical_name] 1844 ir.rvalues = list(set(additional + ir.rvalues)) 1845 1846 # Propage storage ref information if it does not exist 1847 # This can happen if the refers_to variable was discovered through the phi operator on function parameter 1848 # aka you have storage pointer as function parameter 1849 # instead of having a storage pointer for which the aliases belong to the function body 1850 if ( 1851 isinstance(ir, Phi) 1852 and isinstance(ir.lvalue, LocalIRVariable) 1853 and ir.lvalue.is_storage 1854 and not ir.lvalue.refers_to 1855 ): 1856 refers_to = [] 1857 for candidate in ir.rvalues: 1858 if isinstance(candidate, StateIRVariable): 1859 refers_to.append(candidate) 1860 if isinstance(candidate, LocalIRVariable) and candidate.is_storage: 1861 refers_to += candidate.refers_to 1862 1863 ir.lvalue.refers_to = refers_to 1864 1865 node.irs_ssa = [ir for ir in node.irs_ssa if not self._unchange_phi(ir)]