Merge remote-tracking branch 'origin/dev' into dev-function-id-printer-improvements

pull/886/head
Josselin 3 years ago
commit 173ef03eab
  1. 18
      README.md
  2. 8
      setup.py
  3. 31
      slither/__main__.py
  4. 32
      slither/analyses/data_dependency/data_dependency.py
  5. 6
      slither/core/compilation_unit.py
  6. 4
      slither/core/context/context.py
  7. 44
      slither/core/declarations/contract.py
  8. 71
      slither/core/declarations/custom_error.py
  9. 12
      slither/core/declarations/custom_error_contract.py
  10. 6
      slither/core/declarations/custom_error_top_level.py
  11. 11
      slither/core/declarations/function.py
  12. 3
      slither/core/declarations/function_contract.py
  13. 3
      slither/core/declarations/function_top_level.py
  14. 22
      slither/core/declarations/import_directive.py
  15. 41
      slither/core/declarations/solidity_import_placeholder.py
  16. 35
      slither/core/declarations/solidity_variables.py
  17. 10
      slither/core/expressions/assignment_operation.py
  18. 2
      slither/core/expressions/expression.py
  19. 2
      slither/core/source_mapping/source_mapping.py
  20. 14
      slither/core/variables/variable.py
  21. 63
      slither/detectors/abstract_detector.py
  22. 2
      slither/detectors/erc/unindexed_event_parameters.py
  23. 16
      slither/detectors/functions/arbitrary_send.py
  24. 18
      slither/detectors/operations/bad_prng.py
  25. 19
      slither/detectors/shadowing/abstract.py
  26. 2
      slither/detectors/statements/assert_state_change.py
  27. 76
      slither/detectors/statements/calls_in_loop.py
  28. 83
      slither/detectors/statements/costly_operations_in_loop.py
  29. 4
      slither/detectors/statements/incorrect_strict_equality.py
  30. 2
      slither/detectors/variables/possible_const_state_variables.py
  31. 24
      slither/formatters/utils/patches.py
  32. 11
      slither/printers/call/call_graph.py
  33. 3
      slither/printers/inheritance/inheritance.py
  34. 91
      slither/slithir/convert.py
  35. 9
      slither/slithir/operations/internal_call.py
  36. 5
      slither/slithir/operations/member.py
  37. 2
      slither/slithir/tmp_operations/tmp_call.py
  38. 10
      slither/slithir/utils/ssa.py
  39. 30
      slither/solc_parsing/declarations/contract.py
  40. 101
      slither/solc_parsing/declarations/custom_error.py
  41. 14
      slither/solc_parsing/declarations/function.py
  42. 367
      slither/solc_parsing/expressions/expression_parsing.py
  43. 402
      slither/solc_parsing/expressions/find_variable.py
  44. 44
      slither/solc_parsing/slither_compilation_unit_solc.py
  45. 22
      slither/solc_parsing/solidity_types/type_parsing.py
  46. 8
      slither/solc_parsing/variables/variable_declaration.py
  47. 45
      slither/solc_parsing/yul/parse_yul.py
  48. 6
      slither/tools/flattening/flattening.py
  49. 7
      slither/tools/kspec_coverage/analysis.py
  50. 8
      slither/tools/mutator/mutators/abstract_mutator.py
  51. 9
      slither/tools/possible_paths/__main__.py
  52. 2
      slither/tools/upgradeability/__main__.py
  53. 37
      slither/tools/upgradeability/checks/abstract_checks.py
  54. 1
      slither/utils/command_line.py
  55. 4
      slither/utils/myprettytable.py
  56. 126
      slither/utils/output.py
  57. BIN
      tests/ast-parsing/compile/assembly-0.4.0-legacy.zip
  58. BIN
      tests/ast-parsing/compile/assembly-0.4.1-legacy.zip
  59. BIN
      tests/ast-parsing/compile/assembly-0.4.10-legacy.zip
  60. BIN
      tests/ast-parsing/compile/assembly-0.4.11-legacy.zip
  61. BIN
      tests/ast-parsing/compile/assembly-0.4.12-compact.zip
  62. BIN
      tests/ast-parsing/compile/assembly-0.4.12-legacy.zip
  63. BIN
      tests/ast-parsing/compile/assembly-0.4.13-compact.zip
  64. BIN
      tests/ast-parsing/compile/assembly-0.4.13-legacy.zip
  65. BIN
      tests/ast-parsing/compile/assembly-0.4.14-compact.zip
  66. BIN
      tests/ast-parsing/compile/assembly-0.4.14-legacy.zip
  67. BIN
      tests/ast-parsing/compile/assembly-0.4.15-compact.zip
  68. BIN
      tests/ast-parsing/compile/assembly-0.4.15-legacy.zip
  69. BIN
      tests/ast-parsing/compile/assembly-0.4.16-compact.zip
  70. BIN
      tests/ast-parsing/compile/assembly-0.4.16-legacy.zip
  71. BIN
      tests/ast-parsing/compile/assembly-0.4.17-compact.zip
  72. BIN
      tests/ast-parsing/compile/assembly-0.4.17-legacy.zip
  73. BIN
      tests/ast-parsing/compile/assembly-0.4.18-compact.zip
  74. BIN
      tests/ast-parsing/compile/assembly-0.4.18-legacy.zip
  75. BIN
      tests/ast-parsing/compile/assembly-0.4.19-compact.zip
  76. BIN
      tests/ast-parsing/compile/assembly-0.4.19-legacy.zip
  77. BIN
      tests/ast-parsing/compile/assembly-0.4.2-legacy.zip
  78. BIN
      tests/ast-parsing/compile/assembly-0.4.20-compact.zip
  79. BIN
      tests/ast-parsing/compile/assembly-0.4.20-legacy.zip
  80. BIN
      tests/ast-parsing/compile/assembly-0.4.21-compact.zip
  81. BIN
      tests/ast-parsing/compile/assembly-0.4.21-legacy.zip
  82. BIN
      tests/ast-parsing/compile/assembly-0.4.22-compact.zip
  83. BIN
      tests/ast-parsing/compile/assembly-0.4.22-legacy.zip
  84. BIN
      tests/ast-parsing/compile/assembly-0.4.23-compact.zip
  85. BIN
      tests/ast-parsing/compile/assembly-0.4.23-legacy.zip
  86. BIN
      tests/ast-parsing/compile/assembly-0.4.24-compact.zip
  87. BIN
      tests/ast-parsing/compile/assembly-0.4.24-legacy.zip
  88. BIN
      tests/ast-parsing/compile/assembly-0.4.25-compact.zip
  89. BIN
      tests/ast-parsing/compile/assembly-0.4.25-legacy.zip
  90. BIN
      tests/ast-parsing/compile/assembly-0.4.26-compact.zip
  91. BIN
      tests/ast-parsing/compile/assembly-0.4.26-legacy.zip
  92. BIN
      tests/ast-parsing/compile/assembly-0.4.3-legacy.zip
  93. BIN
      tests/ast-parsing/compile/assembly-0.4.4-legacy.zip
  94. BIN
      tests/ast-parsing/compile/assembly-0.4.5-legacy.zip
  95. BIN
      tests/ast-parsing/compile/assembly-0.4.6-legacy.zip
  96. BIN
      tests/ast-parsing/compile/assembly-0.4.7-legacy.zip
  97. BIN
      tests/ast-parsing/compile/assembly-0.4.8-legacy.zip
  98. BIN
      tests/ast-parsing/compile/assembly-0.4.9-legacy.zip
  99. BIN
      tests/ast-parsing/compile/assembly-0.5.0-compact.zip
  100. BIN
      tests/ast-parsing/compile/assembly-0.5.0-legacy.zip
  101. Some files were not shown because too many files have changed in this diff Show More

@ -199,7 +199,7 @@ Feel free to stop by our [Slack channel](https://empireslacking.herokuapp.com) (
* The [Detector documentation](https://github.com/trailofbits/slither/wiki/Adding-a-new-detector) describes how to write a new vulnerability analyses.
* The [API documentation](https://github.com/trailofbits/slither/wiki/API-examples) describes the methods and objects available for custom analyses.
* The [API documentation](https://github.com/crytic/slither/wiki/Python-API) describes the methods and objects available for custom analyses.
* The [SlithIR documentation](https://github.com/trailofbits/slither/wiki/SlithIR) describes the SlithIR intermediate representation.
@ -214,12 +214,14 @@ Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailt
- [Slither: A Static Analysis Framework For Smart Contracts](https://arxiv.org/abs/1908.09878), Josselin Feist, Gustavo Grieco, Alex Groce - WETSEB '19
### External publications
- [ReJection: A AST-Based Reentrancy Vulnerability Detection Method](https://www.researchgate.net/publication/339354823_ReJection_A_AST-Based_Reentrancy_Vulnerability_Detection_Method), Rui Ma, Zefeng Jian, Guangyuan Chen, Ke Ma, Yujia Chen - CTCIS 19
- [MPro: Combining Static and Symbolic Analysis forScalable Testing of Smart Contract](https://arxiv.org/pdf/1911.00570.pdf), William Zhang, Sebastian Banescu, Leodardo Pasos, Steven Stewart, Vijay Ganesh - ISSRE 2019
- [ETHPLOIT: From Fuzzing to Efficient Exploit Generation against Smart Contracts](https://wcventure.github.io/FuzzingPaper/Paper/SANER20_ETHPLOIT.pdf), Qingzhao Zhang, Yizhuo Wang, Juanru Li, Siqi Ma - SANER 20
- [Verification of Ethereum Smart Contracts: A Model Checking Approach](http://www.ijmlc.org/vol10/977-AM0059.pdf), Tam Bang, Hoang H Nguyen, Dung Nguyen, Toan Trieu, Tho Quan - IJMLC 20
- [Smart Contract Repair](https://arxiv.org/pdf/1912.05823.pdf), Xiao Liang Yu, Omar Al-Bataineh, David Lo, Abhik Roychoudhury - TOSEM 20
- [Demystifying Loops in Smart Contracts](https://www.microsoft.com/en-us/research/uploads/prod/2020/08/loops_solidity__camera_ready-5f3fec3f15c69.pdf), Ben Mariano, Yanju Chen, Yu Feng, Shuvendu Lahiri, Isil Dillig - ASE 20
- [Trace-Based Dynamic Gas Estimation of Loops in Smart Contracts](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9268144), Chunmiao Li, Shijie Nie, Yang Cao, Yijun Yu, Zhenjiang Hu - IEEE Open J. Comput. Soc. 1 (2020)
Title | Usage | Authors | Venue
--- | --- | --- | ---
[ReJection: A AST-Based Reentrancy Vulnerability Detection Method](https://www.researchgate.net/publication/339354823_ReJection_A_AST-Based_Reentrancy_Vulnerability_Detection_Method) | AST-based analysis built on top of Slither | Rui Ma, Zefeng Jian, Guangyuan Chen, Ke Ma, Yujia Chen | CTCIS 19
[MPro: Combining Static and Symbolic Analysis forScalable Testing of Smart Contract](https://arxiv.org/pdf/1911.00570.pdf) | Leverage data dependency through Slither | William Zhang, Sebastian Banescu, Leodardo Pasos, Steven Stewart, Vijay Ganesh | ISSRE 2019
[ETHPLOIT: From Fuzzing to Efficient Exploit Generation against Smart Contracts](https://wcventure.github.io/FuzzingPaper/Paper/SANER20_ETHPLOIT.pdf) | Leverage data dependency through Slither | Qingzhao Zhang, Yizhuo Wang, Juanru Li, Siqi Ma | SANER 20
[Verification of Ethereum Smart Contracts: A Model Checking Approach](http://www.ijmlc.org/vol10/977-AM0059.pdf) | Symbolic execution built on top of Slither’s CFG | Tam Bang, Hoang H Nguyen, Dung Nguyen, Toan Trieu, Tho Quan | IJMLC 20
[Smart Contract Repair](https://arxiv.org/pdf/1912.05823.pdf) | Rely on Slither’s vulnerabilities detectors | Xiao Liang Yu, Omar Al-Bataineh, David Lo, Abhik Roychoudhury | TOSEM 20
[Demystifying Loops in Smart Contracts](https://www.microsoft.com/en-us/research/uploads/prod/2020/08/loops_solidity__camera_ready-5f3fec3f15c69.pdf) | Leverage data dependency through Slither | Ben Mariano, Yanju Chen, Yu Feng, Shuvendu Lahiri, Isil Dillig | ASE 20
[Trace-Based Dynamic Gas Estimation of Loops in Smart Contracts](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9268144) | Use Slither’s CFG to detect loops | Chunmiao Li, Shijie Nie, Yang Cao, Yijun Yu, Zhenjiang Hu | IEEE Open J. Comput. Soc. 1 (2020)
If you are using Slither on an academic work, consider applying to the [Crytic $10k Research Prize](https://blog.trailofbits.com/2019/11/13/announcing-the-crytic-10k-research-prize/).

@ -5,18 +5,18 @@ setup(
description="Slither is a Solidity static analysis framework written in Python 3.",
url="https://github.com/crytic/slither",
author="Trail of Bits",
version="0.8.0",
version="0.8.1",
packages=find_packages(),
python_requires=">=3.6",
install_requires=[
"prettytable>=0.7.2",
"pysha3>=1.0.2",
"crytic-compile>=0.2.0",
# "crytic-compile",
# "crytic-compile>=0.2.1",
"crytic-compile",
],
# dependency_links=["git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile"],
license="AGPL-3.0",
long_description=open("README.md").read(),
long_description=open("README.md", "r", encoding="utf-8").read(),
entry_points={
"console_scripts": [
"slither = slither.__main__:main",

@ -24,9 +24,9 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassi
from slither.printers import all_printers
from slither.printers.abstract_printer import AbstractPrinter
from slither.slither import Slither
from slither.utils.output import output_to_json, output_to_zip, ZIP_TYPES_ACCEPTED
from slither.utils.output import output_to_json, output_to_zip, output_to_sarif, ZIP_TYPES_ACCEPTED
from slither.utils.output_capture import StandardOutputCapture
from slither.utils.colors import red, blue, set_colorization_enabled
from slither.utils.colors import red, set_colorization_enabled
from slither.utils.command_line import (
output_detectors,
output_results_to_markdown,
@ -397,6 +397,13 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
default=defaults_flag_in_config["json"],
)
group_misc.add_argument(
"--sarif",
help='Export the results as a SARIF JSON file ("--sarif -" to export to stdout)',
action="store",
default=defaults_flag_in_config["sarif"],
)
group_misc.add_argument(
"--json-types",
help="Comma-separated list of result types to output to JSON, defaults to "
@ -645,6 +652,8 @@ def main_impl(all_detector_classes, all_printer_classes):
output_error = None
outputting_json = args.json is not None
outputting_json_stdout = args.json == "-"
outputting_sarif = args.sarif is not None
outputting_sarif_stdout = args.sarif == "-"
outputting_zip = args.zip is not None
if args.zip_type not in ZIP_TYPES_ACCEPTED.keys():
to_log = f'Zip type not accepted, it must be one of {",".join(ZIP_TYPES_ACCEPTED.keys())}'
@ -652,8 +661,8 @@ def main_impl(all_detector_classes, all_printer_classes):
# If we are outputting JSON, capture all standard output. If we are outputting to stdout, we block typical stdout
# output.
if outputting_json:
StandardOutputCapture.enable(outputting_json_stdout)
if outputting_json or output_to_sarif:
StandardOutputCapture.enable(outputting_json_stdout or outputting_sarif_stdout)
printer_classes = choose_printers(args, all_printer_classes)
detector_classes = choose_detectors(args, all_detector_classes)
@ -732,7 +741,7 @@ def main_impl(all_detector_classes, all_printer_classes):
) = process_all(filename, args, detector_classes, printer_classes)
# Determine if we are outputting JSON
if outputting_json or outputting_zip:
if outputting_json or outputting_zip or output_to_sarif:
# Add our compilation information to JSON
if "compilations" in args.json_types:
compilation_results = []
@ -777,12 +786,6 @@ def main_impl(all_detector_classes, all_printer_classes):
len(detector_classes),
len(results_detectors),
)
logger.info(
blue(
"Use https://crytic.io/ to get access to additional detectors and Github integration"
)
)
if args.ignore_return_value:
return
@ -809,6 +812,12 @@ def main_impl(all_detector_classes, all_printer_classes):
StandardOutputCapture.disable()
output_to_json(None if outputting_json_stdout else args.json, output_error, json_results)
if outputting_sarif:
StandardOutputCapture.disable()
output_to_sarif(
None if outputting_sarif_stdout else args.sarif, json_results, detector_classes
)
if outputting_zip:
output_to_zip(args.zip, output_error, json_results, args.zip_type)

@ -13,6 +13,7 @@ from slither.core.declarations import (
SolidityVariableComposed,
Structure,
)
from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder
from slither.core.variables.variable import Variable
from slither.slithir.operations import Index, OperationWithLValue, InternalCall
from slither.slithir.variables import (
@ -37,7 +38,12 @@ if TYPE_CHECKING:
###################################################################################
def is_dependent(variable, source, context, only_unprotected=False):
def is_dependent(
variable: Variable,
source: Variable,
context: Union[Contract, Function],
only_unprotected: bool = False,
) -> bool:
"""
Args:
variable (Variable)
@ -52,17 +58,22 @@ def is_dependent(variable, source, context, only_unprotected=False):
return False
if variable == source:
return True
context = context.context
context_dict = context.context
if only_unprotected:
return (
variable in context[KEY_NON_SSA_UNPROTECTED]
and source in context[KEY_NON_SSA_UNPROTECTED][variable]
variable in context_dict[KEY_NON_SSA_UNPROTECTED]
and source in context_dict[KEY_NON_SSA_UNPROTECTED][variable]
)
return variable in context[KEY_NON_SSA] and source in context[KEY_NON_SSA][variable]
return variable in context_dict[KEY_NON_SSA] and source in context_dict[KEY_NON_SSA][variable]
def is_dependent_ssa(variable, source, context, only_unprotected=False):
def is_dependent_ssa(
variable: Variable,
source: Variable,
context: Union[Contract, Function],
only_unprotected: bool = False,
) -> bool:
"""
Args:
variable (Variable)
@ -73,17 +84,17 @@ def is_dependent_ssa(variable, source, context, only_unprotected=False):
bool
"""
assert isinstance(context, (Contract, Function))
context = context.context
context_dict = context.context
if isinstance(variable, Constant):
return False
if variable == source:
return True
if only_unprotected:
return (
variable in context[KEY_SSA_UNPROTECTED]
and source in context[KEY_SSA_UNPROTECTED][variable]
variable in context_dict[KEY_SSA_UNPROTECTED]
and source in context_dict[KEY_SSA_UNPROTECTED][variable]
)
return variable in context[KEY_SSA] and source in context[KEY_SSA][variable]
return variable in context_dict[KEY_SSA] and source in context_dict[KEY_SSA][variable]
GENERIC_TAINT = {
@ -398,6 +409,7 @@ def convert_variable_to_non_ssa(v):
Structure,
Function,
Type,
SolidityImportPlaceHolder,
),
)
return v

@ -13,6 +13,7 @@ from slither.core.declarations import (
Function,
Modifier,
)
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel
@ -40,6 +41,7 @@ class SlitherCompilationUnit(Context):
self._functions_top_level: List[FunctionTopLevel] = []
self._pragma_directives: List[Pragma] = []
self._import_directives: List[Import] = []
self._custom_errors: List[CustomError] = []
self._all_functions: Set[Function] = set()
self._all_modifiers: Set[Modifier] = set()
@ -210,6 +212,10 @@ class SlitherCompilationUnit(Context):
def functions_top_level(self) -> List[FunctionTopLevel]:
return self._functions_top_level
@property
def custom_errors(self) -> List[CustomError]:
return self._custom_errors
# endregion
###################################################################################
###################################################################################

@ -3,9 +3,9 @@ from typing import Dict
class Context: # pylint: disable=too-few-public-methods
def __init__(self):
def __init__(self) -> None:
super().__init__()
self._context = {"MEMBERS": defaultdict(None)}
self._context: Dict = {"MEMBERS": defaultdict(None)}
@property
def context(self) -> Dict:

@ -11,7 +11,7 @@ from slither.core.cfg.scope import Scope
from slither.core.solidity_types.type import Type
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.declarations.function import Function, FunctionType
from slither.core.declarations.function import Function, FunctionType, FunctionLanguage
from slither.utils.erc import (
ERC20_signatures,
ERC165_signatures,
@ -38,6 +38,7 @@ if TYPE_CHECKING:
from slither.core.variables.variable import Variable
from slither.core.variables.state_variable import StateVariable
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.declarations.custom_error_contract import CustomErrorContract
LOGGER = logging.getLogger("Contract")
@ -68,6 +69,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._modifiers: Dict[str, "Modifier"] = {}
self._functions: Dict[str, "FunctionContract"] = {}
self._linearizedBaseContracts: List[int] = []
self._custom_errors: Dict[str:"CustomErrorContract"] = {}
# The only str is "*"
self._using_for: Dict[Union[str, Type], List[str]] = {}
@ -242,6 +244,38 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def using_for(self) -> Dict[Union[str, Type], List[str]]:
return self._using_for
# endregion
###################################################################################
###################################################################################
# region Custom Errors
###################################################################################
###################################################################################
@property
def custom_errors(self) -> List["CustomErrorContract"]:
"""
list(CustomErrorContract): List of the contract's custom errors
"""
return list(self._custom_errors.values())
@property
def custom_errors_inherited(self) -> List["CustomErrorContract"]:
"""
list(CustomErrorContract): List of the inherited custom errors
"""
return [s for s in self.custom_errors if s.contract != self]
@property
def custom_errors_declared(self) -> List["CustomErrorContract"]:
"""
list(CustomErrorContract): List of the custom errors declared within the contract (not inherited)
"""
return [s for s in self.custom_errors if s.contract == self]
@property
def custom_errors_as_dict(self) -> Dict[str, "CustomErrorContract"]:
return self._custom_errors
# endregion
###################################################################################
###################################################################################
@ -506,7 +540,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def available_elements_from_inheritances(
self,
elements: Dict[str, "Function"],
getter_available: Callable[["Contract"], List["Function"]],
getter_available: Callable[["Contract"], List["FunctionContract"]],
) -> Dict[str, "Function"]:
"""
@ -517,14 +551,16 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
# keep track of the contracts visited
# to prevent an ovveride due to multiple inheritance of the same contract
# A is B, C, D is C, --> the second C was already seen
inherited_elements: Dict[str, "Function"] = {}
inherited_elements: Dict[str, "FunctionContract"] = {}
accessible_elements = {}
contracts_visited = []
for father in self.inheritance_reverse:
functions: Dict[str, "Function"] = {
functions: Dict[str, "FunctionContract"] = {
v.full_name: v
for v in getter_available(father)
if v.contract not in contracts_visited
and v.function_language
!= FunctionLanguage.Yul # Yul functions are not propagated in the inheritance
}
contracts_visited.append(father)
inherited_elements.update(functions)

@ -0,0 +1,71 @@
from typing import List, TYPE_CHECKING, Optional, Type, Union
from slither.core.solidity_types import UserDefinedType
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.local_variable import LocalVariable
if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
class CustomError(SourceMapping):
def __init__(self, compilation_unit: "SlitherCompilationUnit"):
super().__init__()
self._name: str = ""
self._parameters: List[LocalVariable] = []
self._compilation_unit = compilation_unit
self._solidity_signature: Optional[str] = None
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, new_name: str) -> None:
self._name = new_name
@property
def parameters(self) -> List[LocalVariable]:
return self._parameters
def add_parameters(self, p: "LocalVariable"):
self._parameters.append(p)
@property
def compilation_unit(self) -> "SlitherCompilationUnit":
return self._compilation_unit
# region Signature
###################################################################################
###################################################################################
@staticmethod
def _convert_type_for_solidity_signature(t: Optional[Union[Type, List[Type]]]):
# pylint: disable=import-outside-toplevel
from slither.core.declarations import Contract
if isinstance(t, UserDefinedType) and isinstance(t.type, Contract):
return "address"
return str(t)
@property
def solidity_signature(self) -> str:
"""
Return a signature following the Solidity Standard
Contract and converted into address
:return: the solidity signature
"""
if self._solidity_signature is None:
parameters = [
self._convert_type_for_solidity_signature(x.type) for x in self.parameters
]
self._solidity_signature = self.name + "(" + ",".join(parameters) + ")"
return self._solidity_signature
# endregion
###################################################################################
###################################################################################
def __str__(self):
return "revert " + self.solidity_signature

@ -0,0 +1,12 @@
from slither.core.children.child_contract import ChildContract
from slither.core.declarations.custom_error import CustomError
class CustomErrorContract(CustomError, ChildContract):
def is_declared_by(self, contract):
"""
Check if the element is declared by the contract
:param contract:
:return:
"""
return self.contract == contract

@ -0,0 +1,6 @@
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.top_level import TopLevel
class CustomErrorTopLevel(CustomError, TopLevel):
pass

@ -104,7 +104,13 @@ def _filter_state_variables_written(expressions: List["Expression"]):
return ret
class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
class FunctionLanguage(Enum):
Solidity = 0
Yul = 1
Vyper = 2
class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-public-methods
"""
Function class
"""
@ -207,6 +213,9 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
self.compilation_unit: "SlitherCompilationUnit" = compilation_unit
# Assume we are analyzing Solidty by default
self.function_language: FunctionLanguage = FunctionLanguage.Solidity
###################################################################################
###################################################################################
# region General properties

@ -5,7 +5,6 @@ from typing import TYPE_CHECKING, List, Tuple
from slither.core.children.child_contract import ChildContract
from slither.core.children.child_inheritance import ChildInheritance
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.declarations import Function
# pylint: disable=import-outside-toplevel,too-many-instance-attributes,too-many-statements,too-many-lines
@ -14,7 +13,7 @@ if TYPE_CHECKING:
from slither.core.declarations import Contract
class FunctionContract(Function, ChildContract, ChildInheritance, SourceMapping):
class FunctionContract(Function, ChildContract, ChildInheritance):
@property
def canonical_name(self) -> str:
"""

@ -5,10 +5,9 @@ from typing import List, Tuple
from slither.core.declarations import Function
from slither.core.declarations.top_level import TopLevel
from slither.core.source_mapping.source_mapping import SourceMapping
class FunctionTopLevel(Function, TopLevel, SourceMapping):
class FunctionTopLevel(Function, TopLevel):
@property
def canonical_name(self) -> str:
"""

@ -1,16 +1,34 @@
from pathlib import Path
from typing import Optional
from slither.core.source_mapping.source_mapping import SourceMapping
class Import(SourceMapping):
def __init__(self, filename: str):
def __init__(self, filename: Path):
super().__init__()
self._filename = filename
self._filename: Path = filename
self._alias: Optional[str] = None
@property
def filename(self) -> str:
"""
Return the absolute filename
:return:
:rtype:
"""
return str(self._filename)
@property
def filename_path(self) -> Path:
"""
Return the absolute filename
:return:
:rtype:
"""
return self._filename
@property

@ -0,0 +1,41 @@
"""
Special variable to model import with renaming
"""
from slither.core.declarations import Import
from slither.core.solidity_types import ElementaryType
from slither.core.variables.variable import Variable
class SolidityImportPlaceHolder(Variable):
"""
Placeholder for import on top level objects
See the example at https://blog.soliditylang.org/2020/09/02/solidity-0.7.1-release-announcement/
In the long term we should remove this and better integrate import aliases
"""
def __init__(self, import_directive: Import):
super().__init__()
assert import_directive.alias is not None
self._import_directive = import_directive
self._name = import_directive.alias
self._type = ElementaryType("string")
self._initialized = True
self._visibility = "private"
self._is_constant = True
@property
def type(self) -> ElementaryType:
return ElementaryType("string")
def __eq__(self, other):
return (
self.__class__ == other.__class__
and self._import_directive.filename == self._import_directive.filename
)
@property
def import_directive(self) -> Import:
return self._import_directive
def __hash__(self):
return hash(str(self.import_directive))

@ -2,11 +2,12 @@
from typing import List, Dict, Union, TYPE_CHECKING
from slither.core.context.context import Context
from slither.core.declarations.custom_error import CustomError
from slither.core.solidity_types import ElementaryType, TypeInformation
from slither.exceptions import SlitherException
if TYPE_CHECKING:
from slither.core.declarations import Import
pass
SOLIDITY_VARIABLES = {
"now": "uint256",
@ -42,6 +43,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"require(bool,string)": [],
"revert()": [],
"revert(string)": [],
"revert ": [],
"addmod(uint256,uint256,uint256)": ["uint256"],
"mulmod(uint256,uint256,uint256)": ["uint256"],
"keccak256()": ["bytes32"],
@ -186,35 +188,18 @@ class SolidityFunction:
return hash(self.name)
class SolidityImportPlaceHolder(SolidityVariable):
"""
Placeholder for import on top level objects
See the example at https://blog.soliditylang.org/2020/09/02/solidity-0.7.1-release-announcement/
In the long term we should remove this and better integrate import aliases
"""
def __init__(self, import_directive: "Import"):
assert import_directive.alias is not None
super().__init__(import_directive.alias)
self._import_directive = import_directive
def _check_name(self, name: str):
return True
@property
def type(self) -> ElementaryType:
return ElementaryType("string")
class SolidityCustomRevert(SolidityFunction):
def __init__(self, custom_error: CustomError): # pylint: disable=super-init-not-called
self._name = "revert " + custom_error.solidity_signature
self._custom_error = custom_error
self._return_type: List[Union[TypeInformation, ElementaryType]] = []
def __eq__(self, other):
return (
self.__class__ == other.__class__
and self.name == other.name
and self._import_directive.filename == self._import_directive.filename
and self._custom_error == other._custom_error
)
@property
def import_directive(self) -> "Import":
return self._import_directive
def __hash__(self):
return hash(str(self.import_directive))
return hash(hash(self.name) + hash(self._custom_error))

@ -26,7 +26,7 @@ class AssignmentOperationType(Enum):
ASSIGN_MODULO = 10 # %=
@staticmethod
def get_type(operation_type: "AssignmentOperationType"):
def get_type(operation_type: str) -> "AssignmentOperationType":
if operation_type == "=":
return AssignmentOperationType.ASSIGN
if operation_type == "|=":
@ -52,7 +52,7 @@ class AssignmentOperationType(Enum):
raise SlitherCoreError("get_type: Unknown operation type {})".format(operation_type))
def __str__(self):
def __str__(self) -> str:
if self == AssignmentOperationType.ASSIGN:
return "="
if self == AssignmentOperationType.ASSIGN_OR:
@ -91,7 +91,7 @@ class AssignmentOperation(ExpressionTyped):
super().__init__()
left_expression.set_lvalue()
self._expressions = [left_expression, right_expression]
self._type: Optional["Type"] = expression_type
self._type: Optional["AssignmentOperationType"] = expression_type
self._expression_return_type: Optional["Type"] = expression_return_type
@property
@ -111,8 +111,8 @@ class AssignmentOperation(ExpressionTyped):
return self._expressions[1]
@property
def type(self) -> Optional["Type"]:
def type(self) -> Optional["AssignmentOperationType"]:
return self._type
def __str__(self):
def __str__(self) -> str:
return str(self.expression_left) + " " + str(self.type) + " " + str(self.expression_right)

@ -10,5 +10,5 @@ class Expression(SourceMapping):
def is_lvalue(self) -> bool:
return self._is_lvalue
def set_lvalue(self):
def set_lvalue(self) -> None:
self._is_lvalue = True

@ -8,7 +8,7 @@ if TYPE_CHECKING:
class SourceMapping(Context):
def __init__(self):
def __init__(self) -> None:
super().__init__()
# TODO create a namedtuple for the source mapping rather than a dict
self._source_mapping: Optional[Dict] = None

@ -20,6 +20,7 @@ class Variable(SourceMapping):
self._initialized: Optional[bool] = None
self._visibility: Optional[str] = None
self._is_constant = False
self._is_immutable: bool = False
@property
def is_scalar(self) -> bool:
@ -106,6 +107,19 @@ class Variable(SourceMapping):
assert isinstance(t, (Type, list)) or t is None
self._type = t
@property
def is_immutable(self) -> bool:
"""
Return true of the variable is immutable
:return:
"""
return self._is_immutable
@is_immutable.setter
def is_immutable(self, immutablility: bool):
self._is_immutable = immutablility
@property
def function_name(self):
"""

@ -1,6 +1,7 @@
import abc
import re
from typing import Optional, List, TYPE_CHECKING
from logging import Logger
from typing import Optional, List, TYPE_CHECKING, Dict, Union, Callable
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.declarations import Contract
@ -8,7 +9,7 @@ from slither.utils.colors import green, yellow, red
from slither.formatters.exceptions import FormatImpossible
from slither.formatters.utils.patches import apply_patch, create_diff
from slither.utils.comparable_enum import ComparableEnum
from slither.utils.output import Output
from slither.utils.output import Output, SupportedOutput
if TYPE_CHECKING:
from slither import Slither
@ -25,8 +26,10 @@ class DetectorClassification(ComparableEnum):
INFORMATIONAL = 3
OPTIMIZATION = 4
UNIMPLEMENTED = 999
classification_colors = {
classification_colors: Dict[DetectorClassification, Callable[[str], str]] = {
DetectorClassification.INFORMATIONAL: green,
DetectorClassification.OPTIMIZATION: green,
DetectorClassification.LOW: green,
@ -46,8 +49,8 @@ classification_txt = {
class AbstractDetector(metaclass=abc.ABCMeta):
ARGUMENT = "" # run the detector with slither.py --ARGUMENT
HELP = "" # help information
IMPACT: Optional[DetectorClassification] = None
CONFIDENCE: Optional[DetectorClassification] = None
IMPACT: DetectorClassification = DetectorClassification.UNIMPLEMENTED
CONFIDENCE: DetectorClassification = DetectorClassification.UNIMPLEMENTED
WIKI = ""
@ -58,7 +61,9 @@ class AbstractDetector(metaclass=abc.ABCMeta):
STANDARD_JSON = True
def __init__(self, compilation_unit: SlitherCompilationUnit, slither, logger):
def __init__(
self, compilation_unit: SlitherCompilationUnit, slither: "Slither", logger: Logger
):
self.compilation_unit: SlitherCompilationUnit = compilation_unit
self.contracts: List[Contract] = compilation_unit.contracts
self.slither: "Slither" = slither
@ -130,32 +135,25 @@ class AbstractDetector(metaclass=abc.ABCMeta):
"CONFIDENCE is not initialized {}".format(self.__class__.__name__)
)
def _log(self, info):
def _log(self, info: str) -> None:
if self.logger:
self.logger.info(self.color(info))
@abc.abstractmethod
def _detect(self):
def _detect(self) -> List[Output]:
"""TODO Documentation"""
return []
# pylint: disable=too-many-branches
def detect(self):
results = []
def detect(self) -> List[Dict]:
results: List[Dict] = []
# only keep valid result, and remove dupplicate
# Keep only dictionaries
for r in [r.data for r in self._detect()]:
for r in [output.data for output in self._detect()]:
if self.compilation_unit.core.valid_result(r) and r not in results:
results.append(r)
if results:
if self.logger:
info = "\n"
for idx, result in enumerate(results):
if self.slither.triage_mode:
info += "{}: ".format(idx)
info += result["description"]
info += "Reference: {}".format(self.WIKI)
self._log(info)
if results and self.logger:
self._log_result(results)
if self.compilation_unit.core.generate_patches:
for result in results:
try:
@ -205,20 +203,24 @@ class AbstractDetector(metaclass=abc.ABCMeta):
if indexes.endswith("]"):
indexes = indexes[:-1]
try:
indexes = [int(i) for i in indexes.split(",")]
indexes_converted = [int(i) for i in indexes.split(",")]
self.slither.save_results_to_hide(
[r for (idx, r) in enumerate(results) if idx in indexes]
[r for (idx, r) in enumerate(results) if idx in indexes_converted]
)
return [r for (idx, r) in enumerate(results) if idx not in indexes]
return [r for (idx, r) in enumerate(results) if idx not in indexes_converted]
except ValueError:
self.logger.error(yellow("Malformed input. Example of valid input: 0,1,2,3"))
return results
@property
def color(self):
def color(self) -> Callable[[str], str]:
return classification_colors[self.IMPACT]
def generate_result(self, info, additional_fields=None):
def generate_result(
self,
info: Union[str, List[Union[str, SupportedOutput]]],
additional_fields: Optional[Dict] = None,
) -> Output:
output = Output(
info,
additional_fields,
@ -233,6 +235,15 @@ class AbstractDetector(metaclass=abc.ABCMeta):
return output
@staticmethod
def _format(_compilation_unit: SlitherCompilationUnit, _result):
def _format(_compilation_unit: SlitherCompilationUnit, _result: Dict) -> None:
"""Implement format"""
return
def _log_result(self, results: List[Dict]) -> None:
info = "\n"
for idx, result in enumerate(results):
if self.slither.triage_mode:
info += "{}: ".format(idx)
info += result["description"]
info += "Reference: {}".format(self.WIKI)
self._log(info)

@ -16,7 +16,7 @@ class UnindexedERC20EventParameters(AbstractDetector):
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters"
WIKI_TITLE = "Unindexed ERC20 event oarameters"
WIKI_TITLE = "Unindexed ERC20 event parameters"
WIKI_DESCRIPTION = "Detects whether events defined by the `ERC20` specification that should have some parameters as `indexed` are missing the `indexed` keyword."
# region wiki_exploit_scenario

@ -9,7 +9,10 @@
TODO: dont report if the value is tainted by msg.value
"""
from slither.core.declarations import Function
from typing import List
from slither.core.cfg.node import Node
from slither.core.declarations import Function, Contract
from slither.analyses.data_dependency.data_dependency import is_tainted, is_dependent
from slither.core.declarations.solidity_variables import (
SolidityFunction,
@ -27,11 +30,14 @@ from slither.slithir.operations import (
# pylint: disable=too-many-nested-blocks,too-many-branches
def arbitrary_send(func):
from slither.utils.output import Output
def arbitrary_send(func: Function):
if func.is_protected():
return []
ret = []
ret: List[Node] = []
for node in func.nodes:
for ir in node.irs:
if isinstance(ir, SolidityCall):
@ -68,7 +74,7 @@ def arbitrary_send(func):
return ret
def detect_arbitrary_send(contract):
def detect_arbitrary_send(contract: Contract):
"""
Detect arbitrary send
Args:
@ -114,7 +120,7 @@ Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract
WIKI_RECOMMENDATION = "Ensure that an arbitrary user cannot withdraw unauthorized funds."
def _detect(self):
def _detect(self) -> List[Output]:
""""""
results = []

@ -2,18 +2,24 @@
Module detecting bad PRNG due to the use of block.timestamp, now or blockhash (block.blockhash) as a source of randomness
"""
from typing import List, Tuple
from slither.analyses.data_dependency.data_dependency import is_dependent_ssa
from slither.core.cfg.node import Node
from slither.core.declarations import Function, Contract
from slither.core.declarations.solidity_variables import (
SolidityVariable,
SolidityFunction,
SolidityVariableComposed,
)
from slither.core.variables.variable import Variable
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import BinaryType, Binary
from slither.slithir.operations import SolidityCall
from slither.utils.output import Output, AllSupportedOutput
def collect_return_values_of_bad_PRNG_functions(f):
def collect_return_values_of_bad_PRNG_functions(f: Function) -> List:
"""
Return the return-values of calls to blockhash()
Args:
@ -33,7 +39,7 @@ def collect_return_values_of_bad_PRNG_functions(f):
return values_returned
def contains_bad_PRNG_sources(func, blockhash_ret_values):
def contains_bad_PRNG_sources(func: Function, blockhash_ret_values: List[Variable]) -> List[Node]:
"""
Check if any node in function has a modulus operator and the first operand is dependent on block.timestamp, now or blockhash()
Returns:
@ -57,7 +63,7 @@ def contains_bad_PRNG_sources(func, blockhash_ret_values):
return list(ret)
def detect_bad_PRNG(contract):
def detect_bad_PRNG(contract: Contract) -> List[Tuple[Function, List[Node]]]:
"""
Args:
contract (Contract)
@ -67,7 +73,7 @@ def detect_bad_PRNG(contract):
blockhash_ret_values = []
for f in contract.functions:
blockhash_ret_values += collect_return_values_of_bad_PRNG_functions(f)
ret = []
ret: List[Tuple[Function, List[Node]]] = []
for f in contract.functions:
bad_prng_nodes = contains_bad_PRNG_sources(f, blockhash_ret_values)
if bad_prng_nodes:
@ -110,7 +116,7 @@ As a result, Eve wins the game."""
"Do not use `block.timestamp`, `now` or `blockhash` as a source of randomness"
)
def _detect(self):
def _detect(self) -> List[Output]:
"""Detect bad PRNG due to the use of block.timestamp, now or blockhash (block.blockhash) as a source of randomness"""
results = []
for c in self.compilation_unit.contracts_derived:
@ -118,7 +124,7 @@ As a result, Eve wins the game."""
for func, nodes in values:
for node in nodes:
info = [func, ' uses a weak PRNG: "', node, '" \n']
info: List[AllSupportedOutput] = [func, ' uses a weak PRNG: "', node, '" \n']
res = self.generate_result(info)
results.append(res)

@ -2,19 +2,24 @@
Module detecting shadowing variables on abstract contract
Recursively check the called functions
"""
from typing import List
from slither.core.declarations import Contract
from slither.core.variables.state_variable import StateVariable
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.utils.output import Output, AllSupportedOutput
def detect_shadowing(contract):
ret = []
def detect_shadowing(contract: Contract) -> List[List[StateVariable]]:
ret: List[List[StateVariable]] = []
variables_fathers = []
for father in contract.inheritance:
if all(not f.is_implemented for f in father.functions + father.modifiers):
if all(not f.is_implemented for f in father.functions + list(father.modifiers)):
variables_fathers += father.state_variables_declared
var: StateVariable
for var in contract.state_variables_declared:
shadow = [v for v in variables_fathers if v.name == var.name]
shadow: List[StateVariable] = [v for v in variables_fathers if v.name == var.name]
if shadow:
ret.append([var] + shadow)
return ret
@ -51,7 +56,7 @@ contract DerivedContract is BaseContract{
WIKI_RECOMMENDATION = "Remove the state variable shadowing."
def _detect(self):
def _detect(self) -> List[Output]:
"""Detect shadowing
Recursively visit the calls
@ -59,14 +64,14 @@ contract DerivedContract is BaseContract{
list: {'vuln', 'filename,'contract','func', 'shadow'}
"""
results = []
results: List[Output] = []
for contract in self.contracts:
shadowing = detect_shadowing(contract)
if shadowing:
for all_variables in shadowing:
shadow = all_variables[0]
variables = all_variables[1:]
info = [shadow, " shadows:\n"]
info: List[AllSupportedOutput] = [shadow, " shadows:\n"]
for var in variables:
info += ["\t- ", var, "\n"]

@ -48,7 +48,7 @@ class AssertStateChange(AbstractDetector):
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change"
WIKI_TITLE = "Assert state shange"
WIKI_TITLE = "Assert state change"
WIKI_DESCRIPTION = """Incorrect use of `assert()`. See Solidity best [practices](https://solidity.readthedocs.io/en/latest/control-structures.html#id4)."""
# region wiki_exploit_scenario

@ -1,14 +1,51 @@
from slither.core.cfg.node import NodeType
from typing import List
from slither.core.cfg.node import NodeType, Node
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations import Contract
from slither.utils.output import Output
from slither.slithir.operations import (
HighLevelCall,
LibraryCall,
LowLevelCall,
Send,
Transfer,
InternalCall,
)
def detect_call_in_loop(contract: Contract) -> List[Node]:
ret: List[Node] = []
for f in contract.functions_entry_points:
if f.is_implemented:
call_in_loop(f.entry_point, 0, [], ret)
return ret
def call_in_loop(node: Node, in_loop_counter: int, visited: List[Node], ret: List[Node]) -> None:
if node in visited:
return
# shared visited
visited.append(node)
if node.type == NodeType.STARTLOOP:
in_loop_counter += 1
elif node.type == NodeType.ENDLOOP:
in_loop_counter -= 1
if in_loop_counter > 0:
for ir in node.all_slithir_operations():
if isinstance(ir, (LowLevelCall, HighLevelCall, Send, Transfer)):
if isinstance(ir, LibraryCall):
continue
ret.append(ir.node)
if isinstance(ir, (InternalCall)):
call_in_loop(ir.function.entry_point, in_loop_counter, visited, ret)
for son in node.sons:
call_in_loop(son, in_loop_counter, visited, ret)
class MultipleCallsInLoop(AbstractDetector):
ARGUMENT = "calls-loop"
@ -45,42 +82,11 @@ If one of the destinations has a fallback function that reverts, `bad` will alwa
WIKI_RECOMMENDATION = "Favor [pull over push](https://github.com/ethereum/wiki/wiki/Safety#favor-pull-over-push-for-external-calls) strategy for external calls."
@staticmethod
def call_in_loop(node, in_loop, visited, ret):
if node in visited:
return
# shared visited
visited.append(node)
if node.type == NodeType.STARTLOOP:
in_loop = True
elif node.type == NodeType.ENDLOOP:
in_loop = False
if in_loop:
for ir in node.irs:
if isinstance(ir, (LowLevelCall, HighLevelCall, Send, Transfer)):
if isinstance(ir, LibraryCall):
continue
ret.append(node)
for son in node.sons:
MultipleCallsInLoop.call_in_loop(son, in_loop, visited, ret)
@staticmethod
def detect_call_in_loop(contract):
ret = []
for f in contract.functions + contract.modifiers:
if f.contract_declarer == contract and f.is_implemented:
MultipleCallsInLoop.call_in_loop(f.entry_point, False, [], ret)
return ret
def _detect(self):
def _detect(self) -> List[Output]:
""""""
results = []
results: List[Output] = []
for c in self.compilation_unit.contracts_derived:
values = self.detect_call_in_loop(c)
values = detect_call_in_loop(c)
for node in values:
func = node.function

@ -1,7 +1,45 @@
from slither.core.cfg.node import NodeType
from slither.core.solidity_types.array_type import ArrayType
from slither.core.solidity_types.mapping_type import MappingType
from typing import List
from slither.core.cfg.node import NodeType, Node
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations import Contract
from slither.utils.output import Output
from slither.slithir.operations import InternalCall, OperationWithLValue
from slither.core.variables.state_variable import StateVariable
def detect_costly_operations_in_loop(contract: Contract) -> List[Node]:
ret: List[Node] = []
for f in contract.functions_entry_points:
if f.is_implemented:
costly_operations_in_loop(f.entry_point, 0, [], ret)
return ret
def costly_operations_in_loop(
node: Node, in_loop_counter: int, visited: List[Node], ret: List[Node]
) -> None:
if node in visited:
return
# shared visited
visited.append(node)
if node.type == NodeType.STARTLOOP:
in_loop_counter += 1
elif node.type == NodeType.ENDLOOP:
in_loop_counter -= 1
if in_loop_counter > 0:
for ir in node.all_slithir_operations():
# Ignore Array/Mapping/Struct types for now
if isinstance(ir, OperationWithLValue) and isinstance(ir.lvalue, StateVariable):
ret.append(ir.node)
break
if isinstance(ir, (InternalCall)):
costly_operations_in_loop(ir.function.entry_point, in_loop_counter, visited, ret)
for son in node.sons:
costly_operations_in_loop(son, in_loop_counter, visited, ret)
class CostlyOperationsInLoop(AbstractDetector):
@ -49,44 +87,11 @@ Incrementing `state_variable` in a loop incurs a lot of gas because of expensive
WIKI_RECOMMENDATION = "Use a local variable to hold the loop computation result."
@staticmethod
def costly_operations_in_loop(node, in_loop, visited, ret):
if node in visited:
return
# shared visited
visited.append(node)
if node.type == NodeType.STARTLOOP:
in_loop = True
elif node.type == NodeType.ENDLOOP:
in_loop = False
if in_loop:
sv_written = node.state_variables_written
for sv in sv_written:
# Ignore Array/Mapping/Struct types for now
if isinstance(sv.type, (ArrayType, MappingType)):
continue
ret.append(node)
break
for son in node.sons:
CostlyOperationsInLoop.costly_operations_in_loop(son, in_loop, visited, ret)
@staticmethod
def detect_costly_operations_in_loop(contract):
ret = []
for f in contract.functions + contract.modifiers:
if f.contract_declarer == contract and f.is_implemented:
CostlyOperationsInLoop.costly_operations_in_loop(f.entry_point, False, [], ret)
return ret
def _detect(self):
def _detect(self) -> List[Output]:
""""""
results = []
results: List[Output] = []
for c in self.compilation_unit.contracts_derived:
values = self.detect_costly_operations_in_loop(c)
values = detect_costly_operations_in_loop(c)
for node in values:
func = node.function
info = [func, " has costly operations inside a loop:\n"]

@ -5,6 +5,7 @@
from slither.analyses.data_dependency.data_dependency import is_dependent_ssa
from slither.core.declarations import Function
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import (
Assignment,
@ -106,6 +107,9 @@ contract Crowdsale{
taints += self.sources_taint
for func in funcs:
# Disable the detector on top level function until we have good taint on those
if isinstance(func, FunctionTopLevel):
continue
for node in func.nodes:
for ir in node.irs_ssa:

@ -31,7 +31,7 @@ class ConstCandidateStateVars(AbstractDetector):
@staticmethod
def _valid_candidate(v):
return isinstance(v.type, ElementaryType) and not v.is_constant
return isinstance(v.type, ElementaryType) and not (v.is_constant or v.is_immutable)
# https://solidity.readthedocs.io/en/v0.5.2/contracts.html#constant-state-variables
valid_solidity_function = [

@ -1,9 +1,19 @@
import os
import difflib
from typing import Dict, Tuple, Union
from collections import defaultdict
def create_patch(result, file, start, end, old_str, new_str): # pylint: disable=too-many-arguments
from slither.core.compilation_unit import SlitherCompilationUnit
# pylint: disable=too-many-arguments
def create_patch(
result: Dict,
file: str,
start: int,
end: int,
old_str: Union[str, bytes],
new_str: Union[str, bytes],
) -> None:
if isinstance(old_str, bytes):
old_str = old_str.decode("utf8")
if isinstance(new_str, bytes):
@ -15,7 +25,7 @@ def create_patch(result, file, start, end, old_str, new_str): # pylint: disable
result["patches"][file].append(p)
def apply_patch(original_txt, patch, offset):
def apply_patch(original_txt: bytes, patch: Dict, offset: int) -> Tuple[bytes, int]:
patched_txt = original_txt[: int(patch["start"] + offset)]
patched_txt += patch["new_string"].encode("utf8")
patched_txt += original_txt[int(patch["end"] + offset) :]
@ -25,9 +35,11 @@ def apply_patch(original_txt, patch, offset):
return patched_txt, patch_length_diff + offset
def create_diff(slither, original_txt, patched_txt, filename):
if slither.crytic_compile:
relative_path = slither.crytic_compile.filename_lookup(filename).relative
def create_diff(
compilation_unit: SlitherCompilationUnit, original_txt: bytes, patched_txt: bytes, filename: str
) -> str:
if compilation_unit.crytic_compile:
relative_path = compilation_unit.crytic_compile.filename_lookup(filename).relative
relative_path = os.path.join(".", relative_path)
else:
relative_path = filename

@ -218,7 +218,12 @@ class PrinterCallGraph(AbstractPrinter):
all_contracts_filename = ""
if not filename.endswith(".dot"):
all_contracts_filename = f"{filename}.all_contracts.call-graph.dot"
if filename in ("", "."):
filename = ""
else:
filename += "."
all_contracts_filename = f"{filename}all_contracts.call-graph.dot"
if filename == ".dot":
all_contracts_filename = "all_contracts.dot"
@ -227,7 +232,7 @@ class PrinterCallGraph(AbstractPrinter):
with open(all_contracts_filename, "w", encoding="utf8") as f:
info += f"Call Graph: {all_contracts_filename}\n"
# Avoid dupplicate funcitons due to different compilation unit
# Avoid duplicate functions due to different compilation unit
all_functionss = [
compilation_unit.functions for compilation_unit in self.slither.compilation_units
]
@ -242,7 +247,7 @@ class PrinterCallGraph(AbstractPrinter):
results.append((all_contracts_filename, content))
for derived_contract in self.slither.contracts_derived:
derived_output_filename = f"{filename}.{derived_contract.name}.call-graph.dot"
derived_output_filename = f"{filename}{derived_contract.name}.call-graph.dot"
with open(derived_output_filename, "w", encoding="utf8") as f:
info += f"Call Graph: {derived_output_filename}\n"
content = "\n".join(

@ -30,9 +30,6 @@ class PrinterInheritance(AbstractPrinter):
"""
info = "Inheritance\n"
if not self.contracts:
return []
info += blue("Child_Contract -> ") + green("Immediate_Base_Contracts")
info += green(" [Not_Immediate_Base_Contracts]")

@ -1,4 +1,5 @@
import logging
from pathlib import Path
from typing import List, TYPE_CHECKING, Union, Optional
# pylint: disable= too-many-lines,import-outside-toplevel,too-many-branches,too-many-statements,too-many-nested-blocks
@ -12,7 +13,10 @@ from slither.core.declarations import (
SolidityVariableComposed,
Structure,
)
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.function_contract import FunctionContract
from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder
from slither.core.declarations.solidity_variables import SolidityCustomRevert
from slither.core.expressions import Identifier, Literal
from slither.core.solidity_types import (
ArrayType,
@ -386,7 +390,11 @@ def propagate_type_and_convert_call(result, node):
ins = result[idx]
if isinstance(ins, TmpCall):
new_ins = extract_tmp_call(ins, node.function.contract)
# If the node.function is a FunctionTopLevel, then it does not have a contract
contract = (
node.function.contract if isinstance(node.function, FunctionContract) else None
)
new_ins = extract_tmp_call(ins, contract)
if new_ins:
new_ins.set_node(ins.node)
ins = new_ins
@ -558,7 +566,11 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals
elif isinstance(ir, InternalCall):
# if its not a tuple, return a singleton
if ir.function is None:
convert_type_of_high_and_internal_level_call(ir, node.function.contract)
func = node.function
function_contract = (
func.contract if isinstance(func, FunctionContract) else None
)
convert_type_of_high_and_internal_level_call(ir, function_contract)
return_type = ir.function.return_type
if return_type:
if len(return_type) == 1:
@ -611,7 +623,19 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals
b.set_expression(ir.expression)
b.set_node(ir.node)
return b
if ir.variable_right == "selector" and isinstance(ir.variable_left.type, Function):
if ir.variable_right == "selector" and isinstance(ir.variable_left, (CustomError)):
assignment = Assignment(
ir.lvalue,
Constant(str(get_function_id(ir.variable_left.solidity_signature))),
ElementaryType("bytes4"),
)
assignment.set_expression(ir.expression)
assignment.set_node(ir.node)
assignment.lvalue.set_type(ElementaryType("bytes4"))
return assignment
if ir.variable_right == "selector" and isinstance(
ir.variable_left.type, (Function)
):
assignment = Assignment(
ir.lvalue,
Constant(str(get_function_id(ir.variable_left.type.full_name))),
@ -735,7 +759,7 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals
return None
def extract_tmp_call(ins, contract): # pylint: disable=too-many-locals
def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]): # pylint: disable=too-many-locals
assert isinstance(ins, TmpCall)
if isinstance(ins.called, Variable) and isinstance(ins.called.type, FunctionType):
# If the call is made to a variable member, where the member is this
@ -749,7 +773,7 @@ def extract_tmp_call(ins, contract): # pylint: disable=too-many-locals
return call
if isinstance(ins.ori, Member):
# If there is a call on an inherited contract, it is an internal call or an event
if ins.ori.variable_left in contract.inheritance + [contract]:
if contract and ins.ori.variable_left in contract.inheritance + [contract]:
if str(ins.ori.variable_right) in [f.name for f in contract.functions]:
internalcall = InternalCall(
(ins.ori.variable_right, ins.ori.variable_left.name),
@ -865,6 +889,33 @@ def extract_tmp_call(ins, contract): # pylint: disable=too-many-locals
to_log = "Slither does not support dynamic functions to libraries if functions have the same name"
to_log += f"{[candidate.full_name for candidate in candidates]}"
raise SlithIRError(to_log)
if isinstance(ins.ori.variable_left, SolidityImportPlaceHolder):
# For top level import, where the import statement renames the filename
# See https://blog.soliditylang.org/2020/09/02/solidity-0.7.1-release-announcement/
current_path = Path(ins.ori.variable_left.source_mapping["filename_absolute"]).parent
target = str(
Path(current_path, ins.ori.variable_left.import_directive.filename).absolute()
)
top_level_function_targets = [
f
for f in ins.compilation_unit.functions_top_level
if f.source_mapping["filename_absolute"] == target
and f.name == ins.ori.variable_right
and len(f.parameters) == ins.nbr_arguments
]
internalcall = InternalCall(
(ins.ori.variable_right, ins.ori.variable_left.name),
ins.nbr_arguments,
ins.lvalue,
ins.type_call,
)
internalcall.set_expression(ins.expression)
internalcall.call_id = ins.call_id
internalcall.function_candidates = top_level_function_targets
return internalcall
msgcall = HighLevelCall(
ins.ori.variable_left,
ins.ori.variable_right,
@ -904,6 +955,12 @@ def extract_tmp_call(ins, contract): # pylint: disable=too-many-locals
s.set_expression(ins.expression)
return s
if isinstance(ins.called, CustomError):
sol_function = SolidityCustomRevert(ins.called)
s = SolidityCall(sol_function, ins.nbr_arguments, ins.lvalue, ins.type_call)
s.set_expression(ins.expression)
return s
if isinstance(ins.ori, TmpNewElementaryType):
n = NewElementaryType(ins.ori.type, ins.lvalue)
n.set_expression(ins.expression)
@ -1355,22 +1412,30 @@ def _convert_to_structure_to_list(return_type: Type) -> List[Type]:
return [return_type.type]
def convert_type_of_high_and_internal_level_call(ir: Operation, contract: Contract):
def convert_type_of_high_and_internal_level_call(ir: Operation, contract: Optional[Contract]):
func = None
if isinstance(ir, InternalCall):
candidates = [
f
for f in contract.functions
if f.name == ir.function_name
and f.contract_declarer.name == ir.contract_name
and len(f.parameters) == len(ir.arguments)
]
if ir.function_candidates:
# This path is taken only for SolidityImportPlaceHolder
# Here we have already done a filtering on the potential targets
candidates = ir.function_candidates
else:
candidates = [
f
for f in contract.functions
if f.name == ir.function_name
and f.contract_declarer.name == ir.contract_name
and len(f.parameters) == len(ir.arguments)
]
func = _find_function_from_parameter(ir, candidates)
if not func:
assert contract
func = contract.get_state_variable_from_name(ir.function_name)
else:
assert isinstance(ir, HighLevelCall)
assert contract
candidates = [
f

@ -1,3 +1,4 @@
from typing import Union, Tuple, List, Optional
from slither.core.declarations import Modifier
from slither.core.declarations.function import Function
from slither.core.declarations.function_contract import FunctionContract
@ -6,7 +7,9 @@ from slither.slithir.operations.lvalue import OperationWithLValue
class InternalCall(Call, OperationWithLValue): # pylint: disable=too-many-instance-attributes
def __init__(self, function, nbr_arguments, result, type_call):
def __init__(
self, function: Union[Function, Tuple[str, str]], nbr_arguments, result, type_call
):
super().__init__()
self._contract_name = ""
if isinstance(function, Function):
@ -21,6 +24,10 @@ class InternalCall(Call, OperationWithLValue): # pylint: disable=too-many-insta
self._nbr_arguments = nbr_arguments
self._type_call = type_call
self._lvalue = result
# function_candidates is only used as an helper to retrieve the "function" object
# For top level function called through a import renamed
# See SolidityImportPlaceHolder usages
self.function_candidates: Optional[List[Function]] = None
@property
def read(self):

@ -1,5 +1,7 @@
from slither.core.declarations import Contract, Function
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.enum import Enum
from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder
from slither.slithir.operations.lvalue import OperationWithLValue
from slither.slithir.utils.utils import is_valid_rvalue
from slither.slithir.variables.constant import Constant
@ -20,8 +22,9 @@ class Member(OperationWithLValue):
# }
# }
assert is_valid_rvalue(variable_left) or isinstance(
variable_left, (Contract, Enum, Function)
variable_left, (Contract, Enum, Function, CustomError, SolidityImportPlaceHolder)
)
assert isinstance(variable_right, Constant)
assert isinstance(result, ReferenceVariable)
super().__init__()

@ -5,6 +5,7 @@ from slither.core.declarations import (
SolidityFunction,
Structure,
)
from slither.core.declarations.custom_error import CustomError
from slither.core.variables.variable import Variable
from slither.slithir.operations.lvalue import OperationWithLValue
@ -20,6 +21,7 @@ class TmpCall(OperationWithLValue): # pylint: disable=too-many-instance-attribu
SolidityFunction,
Structure,
Event,
CustomError,
),
)
super().__init__()

@ -9,6 +9,7 @@ from slither.core.declarations import (
SolidityVariable,
Structure,
)
from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder
from slither.core.solidity_types.type import Type
from slither.core.variables.local_variable import LocalVariable
from slither.core.variables.state_variable import StateVariable
@ -283,7 +284,13 @@ def generate_ssa_irs(
if isinstance(new_ir.rvalue, ReferenceVariable):
refers_to = new_ir.rvalue.points_to_origin
new_ir.lvalue.add_refers_to(refers_to)
else:
# Discard Constant
# This can happen on yul, like
# assembly { var.slot = some_value }
# Here we do not keep track of the references as we do not track
# such low level manipulation
# However we could extend our storage model to do so in the future
elif not isinstance(new_ir.rvalue, Constant):
new_ir.lvalue.add_refers_to(new_ir.rvalue)
for succ in node.dominator_successors:
@ -610,6 +617,7 @@ def get(
Structure,
Function,
Type,
SolidityImportPlaceHolder,
),
) # type for abi.decode(.., t)
return variable

@ -3,8 +3,10 @@ from typing import List, Dict, Callable, TYPE_CHECKING, Union, Set
from slither.core.declarations import Modifier, Event, EnumContract, StructureContract, Function
from slither.core.declarations.contract import Contract
from slither.core.declarations.custom_error_contract import CustomErrorContract
from slither.core.declarations.function_contract import FunctionContract
from slither.core.variables.state_variable import StateVariable
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
from slither.solc_parsing.declarations.event import EventSolc
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.modifier import ModifierSolc
@ -35,15 +37,17 @@ class ContractSolc:
self._modifiersNotParsed: List[Dict] = []
self._functions_no_params: List[FunctionSolc] = []
self._modifiers_no_params: List[ModifierSolc] = []
self._eventsNotParsed: List[EventSolc] = []
self._eventsNotParsed: List[Dict] = []
self._variablesNotParsed: List[Dict] = []
self._enumsNotParsed: List[Dict] = []
self._structuresNotParsed: List[Dict] = []
self._usingForNotParsed: List[Dict] = []
self._customErrorParsed: List[Dict] = []
self._functions_parser: List[FunctionSolc] = []
self._modifiers_parser: List[ModifierSolc] = []
self._structures_parser: List[StructureContractSolc] = []
self._custom_errors_parser: List[CustomErrorSolc] = []
self._is_analyzed: bool = False
@ -246,6 +250,8 @@ class ContractSolc:
self._structuresNotParsed.append(item)
elif item[self.get_key()] == "UsingForDirective":
self._usingForNotParsed.append(item)
elif item[self.get_key()] == "ErrorDefinition":
self._customErrorParsed.append(item)
else:
raise ParsingError("Unknown contract item: " + item[self.get_key()])
return
@ -268,6 +274,23 @@ class ContractSolc:
self._parse_struct(struct)
self._structuresNotParsed = None
def _parse_custom_error(self, custom_error: Dict):
ce = CustomErrorContract(self.compilation_unit)
ce.set_contract(self._contract)
ce.set_offset(custom_error["src"], self.compilation_unit)
ce_parser = CustomErrorSolc(ce, custom_error, self._slither_parser)
self._contract.custom_errors_as_dict[ce.name] = ce
self._custom_errors_parser.append(ce_parser)
def parse_custom_errors(self):
for father in self._contract.inheritance_reverse:
self._contract.custom_errors_as_dict.update(father.custom_errors_as_dict)
for custom_error in self._customErrorParsed:
self._parse_custom_error(custom_error)
self._customErrorParsed = None
def parse_state_variables(self):
for father in self._contract.inheritance_reverse:
self._contract.variables_as_dict.update(father.variables_as_dict)
@ -600,6 +623,10 @@ class ContractSolc:
except (VariableNotFound, KeyError) as e:
self.log_incorrect_parsing(f"Missing struct {e}")
def analyze_custom_errors(self):
for custom_error in self._custom_errors_parser:
custom_error.analyze_params()
def analyze_events(self):
try:
for father in self._contract.inheritance_reverse:
@ -640,6 +667,7 @@ class ContractSolc:
self._enumsNotParsed = []
self._structuresNotParsed = []
self._usingForNotParsed = []
self._customErrorParsed = []
# endregion
###################################################################################

@ -0,0 +1,101 @@
from typing import TYPE_CHECKING, Dict
from slither.core.declarations.custom_error import CustomError
from slither.core.variables.local_variable import LocalVariable
from slither.solc_parsing.variables.local_variable import LocalVariableSolc
if TYPE_CHECKING:
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
# Part of the code was copied from the function parsing
# In the long term we should refactor these two classes to merge the duplicated code
class CustomErrorSolc:
def __init__(
self,
custom_error: CustomError,
custom_error_data: dict,
slither_parser: "SlitherCompilationUnitSolc",
):
self._slither_parser: "SlitherCompilationUnitSolc" = slither_parser
self._custom_error = custom_error
custom_error.name = custom_error_data["name"]
self._params_was_analyzed = False
if not self._slither_parser.is_compact_ast:
custom_error_data = custom_error_data["attributes"]
self._custom_error_data = custom_error_data
def analyze_params(self):
# Can be re-analyzed due to inheritance
if self._params_was_analyzed:
return
self._params_was_analyzed = True
if self._slither_parser.is_compact_ast:
params = self._custom_error_data["parameters"]
else:
children = self._custom_error_data[self.get_children("children")]
# It uses to be
# params = children[0]
# returns = children[1]
# But from Solidity 0.6.3 to 0.6.10 (included)
# Comment above a function might be added in the children
child_iter = iter(
[child for child in children if child[self.get_key()] == "ParameterList"]
)
params = next(child_iter)
if params:
self._parse_params(params)
@property
def is_compact_ast(self) -> bool:
return self._slither_parser.is_compact_ast
def get_key(self) -> str:
return self._slither_parser.get_key()
def get_children(self, key: str) -> str:
if self._slither_parser.is_compact_ast:
return key
return "children"
def _parse_params(self, params: Dict):
assert params[self.get_key()] == "ParameterList"
if self._slither_parser.is_compact_ast:
params = params["parameters"]
else:
params = params[self.get_children("children")]
for param in params:
assert param[self.get_key()] == "VariableDeclaration"
local_var = self._add_param(param)
self._custom_error.add_parameters(local_var.underlying_variable)
def _add_param(self, param: Dict) -> LocalVariableSolc:
local_var = LocalVariable()
local_var.set_offset(param["src"], self._slither_parser.compilation_unit)
local_var_parser = LocalVariableSolc(local_var, param)
local_var_parser.analyze(self)
# see https://solidity.readthedocs.io/en/v0.4.24/types.html?highlight=storage%20location#data-location
if local_var.location == "default":
local_var.set_location("memory")
return local_var_parser
@property
def underlying_custom_error(self) -> CustomError:
return self._custom_error
@property
def slither_parser(self) -> "SlitherCompilationUnitSolc":
return self._slither_parser

@ -10,12 +10,11 @@ from slither.core.declarations.function import (
FunctionType,
)
from slither.core.declarations.function_contract import FunctionContract
from slither.core.expressions import AssignmentOperation
from slither.core.variables.local_variable import LocalVariable
from slither.core.variables.local_variable_init_from_tuple import LocalVariableInitFromTuple
from slither.solc_parsing.cfg.node import NodeSolc
from slither.solc_parsing.exceptions import ParsingError
from slither.solc_parsing.expressions.expression_parsing import parse_expression
from slither.solc_parsing.variables.local_variable import LocalVariableSolc
from slither.solc_parsing.variables.local_variable_init_from_tuple import (
@ -26,13 +25,11 @@ from slither.solc_parsing.yul.parse_yul import YulBlock
from slither.utils.expression_manipulations import SplitTernaryExpression
from slither.visitors.expression.export_values import ExportValues
from slither.visitors.expression.has_conditional import HasConditional
from slither.solc_parsing.exceptions import ParsingError
if TYPE_CHECKING:
from slither.core.expressions.expression import Expression
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.core.slither_core import SlitherCore
from slither.core.compilation_unit import SlitherCompilationUnit
@ -1012,6 +1009,15 @@ class FunctionSolc:
node = self._parse_try_catch(statement, node)
# elif name == 'TryCatchClause':
# self._parse_catch(statement, node)
elif name == "RevertStatement":
if self.is_compact_ast:
expression = statement[self.get_children("errorCall")]
else:
expression = statement[self.get_children("errorCall")][0]
new_node = self._new_node(NodeType.EXPRESSION, statement["src"], scope)
new_node.add_unparsed_expression(expression)
link_underlying_nodes(node, new_node)
node = new_node
else:
raise ParsingError("Statement not parsed %s" % name)

@ -1,19 +1,10 @@
import logging
import re
from typing import Dict, TYPE_CHECKING, Optional, Union, List, Tuple
from typing import Dict, TYPE_CHECKING
from slither.core.declarations import Event, Enum, Structure
from slither.core.declarations.contract import Contract
from slither.core.declarations.function import Function
from slither.core.declarations.function_contract import FunctionContract
from slither.core.declarations.solidity_variables import (
SOLIDITY_FUNCTIONS,
SOLIDITY_VARIABLES,
SOLIDITY_VARIABLES_COMPOSED,
SolidityFunction,
SolidityVariable,
SolidityVariableComposed,
SolidityImportPlaceHolder,
)
from slither.core.expressions.assignment_operation import (
AssignmentOperation,
@ -41,359 +32,18 @@ from slither.core.expressions.unary_operation import UnaryOperation, UnaryOperat
from slither.core.solidity_types import (
ArrayType,
ElementaryType,
FunctionType,
MappingType,
)
from slither.core.variables.variable import Variable
from slither.exceptions import SlitherError
from slither.solc_parsing.exceptions import ParsingError, VariableNotFound
from slither.solc_parsing.expressions.find_variable import CallerContext, find_variable
from slither.solc_parsing.solidity_types.type_parsing import UnknownType, parse_type
if TYPE_CHECKING:
from slither.core.expressions.expression import Expression
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.core.compilation_unit import SlitherCompilationUnit
logger = logging.getLogger("ExpressionParsing")
# pylint: disable=anomalous-backslash-in-string,import-outside-toplevel,too-many-branches,too-many-locals
###################################################################################
###################################################################################
# region Helpers
###################################################################################
###################################################################################
CallerContext = Union["ContractSolc", "FunctionSolc"]
def get_pointer_name(variable: Variable):
curr_type = variable.type
while isinstance(curr_type, (ArrayType, MappingType)):
if isinstance(curr_type, ArrayType):
curr_type = curr_type.type
else:
assert isinstance(curr_type, MappingType)
curr_type = curr_type.type_to
if isinstance(curr_type, FunctionType):
return variable.name + curr_type.parameters_signature
return None
def _find_variable_from_ref_declaration(
referenced_declaration: Optional[int],
all_contracts: List["Contract"],
all_functions_parser: List["FunctionSolc"],
) -> Optional[Union[Contract, Function]]:
if referenced_declaration is None:
return None
# id of the contracts is the referenced declaration
# This is not true for the functions, as we dont always have the referenced_declaration
# But maybe we could? (TODO)
for contract_candidate in all_contracts:
if contract_candidate.id == referenced_declaration:
return contract_candidate
for function_candidate in all_functions_parser:
if (
function_candidate.referenced_declaration == referenced_declaration
and not function_candidate.underlying_function.is_shadowed
):
return function_candidate.underlying_function
return None
def _find_variable_in_function_parser(
var_name: str,
function_parser: Optional["FunctionSolc"],
referenced_declaration: Optional[int] = None,
) -> Optional[Variable]:
if function_parser is None:
return None
# We look for variable declared with the referencedDeclaration attr
func_variables_renamed = function_parser.variables_renamed
if referenced_declaration and referenced_declaration in func_variables_renamed:
return func_variables_renamed[referenced_declaration].underlying_variable
# If not found, check for name
func_variables = function_parser.underlying_function.variables_as_dict
if var_name in func_variables:
return func_variables[var_name]
# A local variable can be a pointer
# for example
# function test(function(uint) internal returns(bool) t) interna{
# Will have a local variable t which will match the signature
# t(uint256)
func_variables_ptr = {
get_pointer_name(f): f for f in function_parser.underlying_function.variables
}
if var_name and var_name in func_variables_ptr:
return func_variables_ptr[var_name]
return None
def _find_top_level(
var_name: str, sl: "SlitherCompilationUnit"
) -> Optional[Union[Enum, Structure, SolidityVariable]]:
structures_top_level = sl.structures_top_level
for st in structures_top_level:
if st.name == var_name:
return st
enums_top_level = sl.enums_top_level
for enum in enums_top_level:
if enum.name == var_name:
return enum
for import_directive in sl.import_directives:
if import_directive.alias == var_name:
return SolidityImportPlaceHolder(import_directive)
return None
def _find_in_contract(
var_name: str,
contract: Optional[Contract],
contract_declarer: Optional[Contract],
is_super: bool,
) -> Optional[Union[Variable, Function, Contract, Event, Enum, Structure,]]:
if contract is None or contract_declarer is None:
return None
# variable are looked from the contract declarer
contract_variables = contract_declarer.variables_as_dict
if var_name in contract_variables:
return contract_variables[var_name]
# A state variable can be a pointer
conc_variables_ptr = {get_pointer_name(f): f for f in contract_declarer.variables}
if var_name and var_name in conc_variables_ptr:
return conc_variables_ptr[var_name]
if is_super:
getter_available = lambda f: f.functions_declared
d = {f.canonical_name: f for f in contract.functions}
functions = {
f.full_name: f
for f in contract_declarer.available_elements_from_inheritances(
d, getter_available
).values()
}
else:
functions = contract.available_functions_as_dict()
if var_name in functions:
return functions[var_name]
if is_super:
getter_available = lambda m: m.modifiers_declared
d = {m.canonical_name: m for m in contract.modifiers}
modifiers = {
m.full_name: m
for m in contract_declarer.available_elements_from_inheritances(
d, getter_available
).values()
}
else:
modifiers = contract.available_modifiers_as_dict()
if var_name in modifiers:
return modifiers[var_name]
# structures are looked on the contract declarer
structures = contract.structures_as_dict
if var_name in structures:
return structures[var_name]
events = contract.events_as_dict
if var_name in events:
return events[var_name]
enums = contract.enums_as_dict
if var_name in enums:
return enums[var_name]
# If the enum is refered as its name rather than its canonicalName
enums = {e.name: e for e in contract.enums}
if var_name in enums:
return enums[var_name]
return None
def _find_variable_init(
caller_context: CallerContext,
) -> Tuple[
List[Contract],
Union[List["FunctionSolc"]],
"SlitherCompilationUnit",
"SlitherCompilationUnitSolc",
]:
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.declarations.function import FunctionSolc
direct_contracts: List[Contract]
direct_functions_parser: List[FunctionSolc]
if isinstance(caller_context, SlitherCompilationUnitSolc):
direct_contracts = []
direct_functions_parser = []
sl = caller_context.compilation_unit
sl_parser = caller_context
elif isinstance(caller_context, ContractSolc):
direct_contracts = [caller_context.underlying_contract]
direct_functions_parser = caller_context.functions_parser + caller_context.modifiers_parser
sl = caller_context.slither_parser.compilation_unit
sl_parser = caller_context.slither_parser
elif isinstance(caller_context, FunctionSolc):
if caller_context.contract_parser:
direct_contracts = [caller_context.contract_parser.underlying_contract]
direct_functions_parser = (
caller_context.contract_parser.functions_parser
+ caller_context.contract_parser.modifiers_parser
)
else:
# Top level functions
direct_contracts = []
direct_functions_parser = []
sl = caller_context.underlying_function.compilation_unit
sl_parser = caller_context.slither_parser
else:
raise SlitherError(
f"{type(caller_context)} ({caller_context} is not valid for find_variable"
)
return direct_contracts, direct_functions_parser, sl, sl_parser
def find_variable(
var_name: str,
caller_context: CallerContext,
referenced_declaration: Optional[int] = None,
is_super=False,
) -> Union[
Variable,
Function,
Contract,
SolidityVariable,
SolidityFunction,
Event,
Enum,
Structure,
]:
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.contract import ContractSolc
# variable are looked from the contract declarer
# functions can be shadowed, but are looked from the contract instance, rather than the contract declarer
# the difference between function and variable come from the fact that an internal call, or an variable access
# in a function does not behave similariy, for example in:
# contract C{
# function f(){
# state_var = 1
# f2()
# }
# state_var will refer to C.state_var, no mater if C is inherited
# while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or
# the contract inheriting from C)
# for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact
# structure/enums cannot be shadowed
direct_contracts, direct_functions_parser, sl, sl_parser = _find_variable_init(caller_context)
all_contracts = sl.contracts
all_functions_parser = sl_parser.all_functions_and_modifiers_parser
# Only look for reference declaration in the direct contract, see comment at the end
# Reference looked are split between direct and all
# Because functions are copied between contracts, two functions can have the same ref
# So we need to first look with respect to the direct context
ret = _find_variable_from_ref_declaration(
referenced_declaration, direct_contracts, direct_functions_parser
)
if ret:
return ret
function_parser: Optional[FunctionSolc] = (
caller_context if isinstance(caller_context, FunctionSolc) else None
)
ret = _find_variable_in_function_parser(var_name, function_parser, referenced_declaration)
if ret:
return ret
contract: Optional[Contract] = None
contract_declarer: Optional[Contract] = None
if isinstance(caller_context, ContractSolc):
contract = caller_context.underlying_contract
contract_declarer = caller_context.underlying_contract
elif isinstance(caller_context, FunctionSolc):
underlying_func = caller_context.underlying_function
# If contract_parser is set to None, then underlying_function is a functionContract
assert isinstance(underlying_func, FunctionContract)
contract = underlying_func.contract
contract_declarer = underlying_func.contract_declarer
ret = _find_in_contract(var_name, contract, contract_declarer, is_super)
if ret:
return ret
# Could refer to any enum
all_enumss = [c.enums_as_dict for c in sl.contracts]
all_enums = {k: v for d in all_enumss for k, v in d.items()}
if var_name in all_enums:
return all_enums[var_name]
contracts = sl.contracts_as_dict
if var_name in contracts:
return contracts[var_name]
if var_name in SOLIDITY_VARIABLES:
return SolidityVariable(var_name)
if var_name in SOLIDITY_FUNCTIONS:
return SolidityFunction(var_name)
# Top level must be at the end, if nothing else was found
ret = _find_top_level(var_name, sl)
if ret:
return ret
# Look from reference declaration in all the contracts at the end
# Because they are many instances where this can't be trusted
# For example in
# contract A{
# function _f() internal view returns(uint){
# return 1;
# }
#
# function get() public view returns(uint){
# return _f();
# }
# }
#
# contract B is A{
# function _f() internal view returns(uint){
# return 2;
# }
#
# }
# get's AST will say that the ref declaration for _f() is A._f(), but in the context of B, its not
ret = _find_variable_from_ref_declaration(
referenced_declaration, all_contracts, all_functions_parser
)
if ret:
return ret
raise VariableNotFound("Variable not found: {} (context {})".format(var_name, caller_context))
# endregion
###################################################################################
###################################################################################
# region Filtering
###################################################################################
###################################################################################
@ -415,6 +65,7 @@ def filter_name(value: str) -> str:
value = value.replace(" payable", "")
value = value.replace("function (", "function(")
value = value.replace("returns (", "returns(")
value = value.replace(" calldata", "")
# remove the text remaining after functio(...)
# which should only be ..returns(...)
@ -798,7 +449,9 @@ def parse_expression(expression: Dict, caller_context: CallerContext) -> "Expres
else:
referenced_declaration = None
var = find_variable(value, caller_context, referenced_declaration)
var, was_created = find_variable(value, caller_context, referenced_declaration)
if was_created:
var.set_offset(src, caller_context.compilation_unit)
identifier = Identifier(var)
identifier.set_offset(src, caller_context.compilation_unit)
@ -848,9 +501,11 @@ def parse_expression(expression: Dict, caller_context: CallerContext) -> "Expres
member_expression = parse_expression(children[0], caller_context)
if str(member_expression) == "super":
super_name = parse_super_name(expression, is_compact_ast)
var = find_variable(super_name, caller_context, is_super=True)
var, was_created = find_variable(super_name, caller_context, is_super=True)
if var is None:
raise VariableNotFound("Variable not found: {}".format(super_name))
if was_created:
var.set_offset(src, caller_context.compilation_unit)
sup = SuperIdentifier(var)
sup.set_offset(src, caller_context.compilation_unit)
return sup
@ -974,7 +629,9 @@ def parse_expression(expression: Dict, caller_context: CallerContext) -> "Expres
else:
referenced_declaration = None
var = find_variable(value, caller_context, referenced_declaration)
var, was_created = find_variable(value, caller_context, referenced_declaration)
if was_created:
var.set_offset(src, caller_context.compilation_unit)
identifier = Identifier(var)
identifier.set_offset(src, caller_context.compilation_unit)

@ -0,0 +1,402 @@
from typing import TYPE_CHECKING, Optional, Union, List, Tuple
from slither.core.declarations import Event, Enum, Structure
from slither.core.declarations.contract import Contract
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.function import Function
from slither.core.declarations.function_contract import FunctionContract
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder
from slither.core.declarations.solidity_variables import (
SOLIDITY_FUNCTIONS,
SOLIDITY_VARIABLES,
SolidityFunction,
SolidityVariable,
)
from slither.core.solidity_types import (
ArrayType,
FunctionType,
MappingType,
)
from slither.core.variables.variable import Variable
from slither.exceptions import SlitherError
from slither.solc_parsing.exceptions import VariableNotFound
if TYPE_CHECKING:
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.core.compilation_unit import SlitherCompilationUnit
# pylint: disable=import-outside-toplevel,too-many-branches,too-many-locals
CallerContext = Union["ContractSolc", "FunctionSolc"]
def _get_pointer_name(variable: Variable):
curr_type = variable.type
while isinstance(curr_type, (ArrayType, MappingType)):
if isinstance(curr_type, ArrayType):
curr_type = curr_type.type
else:
assert isinstance(curr_type, MappingType)
curr_type = curr_type.type_to
if isinstance(curr_type, FunctionType):
return variable.name + curr_type.parameters_signature
return None
def _find_variable_from_ref_declaration(
referenced_declaration: Optional[int],
all_contracts: List["Contract"],
all_functions_parser: List["FunctionSolc"],
) -> Optional[Union[Contract, Function]]:
if referenced_declaration is None:
return None
# id of the contracts is the referenced declaration
# This is not true for the functions, as we dont always have the referenced_declaration
# But maybe we could? (TODO)
for contract_candidate in all_contracts:
if contract_candidate.id == referenced_declaration:
return contract_candidate
for function_candidate in all_functions_parser:
if (
function_candidate.referenced_declaration == referenced_declaration
and not function_candidate.underlying_function.is_shadowed
):
return function_candidate.underlying_function
return None
def _find_variable_in_function_parser(
var_name: str,
function_parser: Optional["FunctionSolc"],
referenced_declaration: Optional[int] = None,
) -> Optional[Variable]:
if function_parser is None:
return None
# We look for variable declared with the referencedDeclaration attr
func_variables_renamed = function_parser.variables_renamed
if referenced_declaration and referenced_declaration in func_variables_renamed:
return func_variables_renamed[referenced_declaration].underlying_variable
# If not found, check for name
func_variables = function_parser.underlying_function.variables_as_dict
if var_name in func_variables:
return func_variables[var_name]
# A local variable can be a pointer
# for example
# function test(function(uint) internal returns(bool) t) interna{
# Will have a local variable t which will match the signature
# t(uint256)
func_variables_ptr = {
_get_pointer_name(f): f for f in function_parser.underlying_function.variables
}
if var_name and var_name in func_variables_ptr:
return func_variables_ptr[var_name]
return None
def _find_top_level(
var_name: str, sl: "SlitherCompilationUnit"
) -> Tuple[Optional[Union[Enum, Structure, SolidityImportPlaceHolder, CustomError]], bool]:
"""
Return the top level variable use, and a boolean indicating if the variable returning was cretead
If the variable was created, it has no source_mapping
:param var_name:
:type var_name:
:param sl:
:type sl:
:return:
:rtype:
"""
structures_top_level = sl.structures_top_level
for st in structures_top_level:
if st.name == var_name:
return st, False
enums_top_level = sl.enums_top_level
for enum in enums_top_level:
if enum.name == var_name:
return enum, False
for import_directive in sl.import_directives:
if import_directive.alias == var_name:
new_val = SolidityImportPlaceHolder(import_directive)
return new_val, True
# Note for now solidity prevent two custom error from having the same name
for custom_error in sl.custom_errors:
if custom_error.solidity_signature == var_name:
return custom_error, False
return None, False
def _find_in_contract(
var_name: str,
contract: Optional[Contract],
contract_declarer: Optional[Contract],
is_super: bool,
) -> Optional[Union[Variable, Function, Contract, Event, Enum, Structure, CustomError]]:
if contract is None or contract_declarer is None:
return None
# variable are looked from the contract declarer
contract_variables = contract_declarer.variables_as_dict
if var_name in contract_variables:
return contract_variables[var_name]
# A state variable can be a pointer
conc_variables_ptr = {_get_pointer_name(f): f for f in contract_declarer.variables}
if var_name and var_name in conc_variables_ptr:
return conc_variables_ptr[var_name]
if is_super:
getter_available = lambda f: f.functions_declared
d = {f.canonical_name: f for f in contract.functions}
functions = {
f.full_name: f
for f in contract_declarer.available_elements_from_inheritances(
d, getter_available
).values()
}
else:
functions = contract.available_functions_as_dict()
if var_name in functions:
return functions[var_name]
if is_super:
getter_available = lambda m: m.modifiers_declared
d = {m.canonical_name: m for m in contract.modifiers}
modifiers = {
m.full_name: m
for m in contract_declarer.available_elements_from_inheritances(
d, getter_available
).values()
}
else:
modifiers = contract.available_modifiers_as_dict()
if var_name in modifiers:
return modifiers[var_name]
# structures are looked on the contract declarer
structures = contract.structures_as_dict
if var_name in structures:
return structures[var_name]
events = contract.events_as_dict
if var_name in events:
return events[var_name]
enums = contract.enums_as_dict
if var_name in enums:
return enums[var_name]
# Note: contract.custom_errors_as_dict uses the name (not the sol sig) as key
# This is because when the dic is populated the underlying object is not yet parsed
# As a result, we need to iterate over all the custom errors here instead of using the dict
custom_errors = contract.custom_errors
for custom_error in custom_errors:
if var_name == custom_error.solidity_signature:
return custom_error
# If the enum is refered as its name rather than its canonicalName
enums = {e.name: e for e in contract.enums}
if var_name in enums:
return enums[var_name]
return None
def _find_variable_init(
caller_context: CallerContext,
) -> Tuple[
List[Contract],
Union[List["FunctionSolc"]],
"SlitherCompilationUnit",
"SlitherCompilationUnitSolc",
]:
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.declarations.function import FunctionSolc
direct_contracts: List[Contract]
direct_functions_parser: List[FunctionSolc]
if isinstance(caller_context, SlitherCompilationUnitSolc):
direct_contracts = []
direct_functions_parser = []
sl = caller_context.compilation_unit
sl_parser = caller_context
elif isinstance(caller_context, ContractSolc):
direct_contracts = [caller_context.underlying_contract]
direct_functions_parser = caller_context.functions_parser + caller_context.modifiers_parser
sl = caller_context.slither_parser.compilation_unit
sl_parser = caller_context.slither_parser
elif isinstance(caller_context, FunctionSolc):
if caller_context.contract_parser:
direct_contracts = [caller_context.contract_parser.underlying_contract]
direct_functions_parser = (
caller_context.contract_parser.functions_parser
+ caller_context.contract_parser.modifiers_parser
)
else:
# Top level functions
direct_contracts = []
direct_functions_parser = []
sl = caller_context.underlying_function.compilation_unit
sl_parser = caller_context.slither_parser
else:
raise SlitherError(
f"{type(caller_context)} ({caller_context} is not valid for find_variable"
)
return direct_contracts, direct_functions_parser, sl, sl_parser
def find_variable(
var_name: str,
caller_context: CallerContext,
referenced_declaration: Optional[int] = None,
is_super=False,
) -> Tuple[
Union[
Variable,
Function,
Contract,
SolidityVariable,
SolidityFunction,
Event,
Enum,
Structure,
CustomError,
],
bool,
]:
"""
Return the variable found and a boolean indicating if the variable was created
If the variable was created, it has no source mapping, and it the caller must add it
:param var_name:
:type var_name:
:param caller_context:
:type caller_context:
:param referenced_declaration:
:type referenced_declaration:
:param is_super:
:type is_super:
:return:
:rtype:
"""
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.contract import ContractSolc
# variable are looked from the contract declarer
# functions can be shadowed, but are looked from the contract instance, rather than the contract declarer
# the difference between function and variable come from the fact that an internal call, or an variable access
# in a function does not behave similariy, for example in:
# contract C{
# function f(){
# state_var = 1
# f2()
# }
# state_var will refer to C.state_var, no mater if C is inherited
# while f2() will refer to the function definition of the inherited contract (C.f2() in the context of C, or
# the contract inheriting from C)
# for events it's unclear what should be the behavior, as they can be shadowed, but there is not impact
# structure/enums cannot be shadowed
direct_contracts, direct_functions_parser, sl, sl_parser = _find_variable_init(caller_context)
all_contracts = sl.contracts
all_functions_parser = sl_parser.all_functions_and_modifiers_parser
# Only look for reference declaration in the direct contract, see comment at the end
# Reference looked are split between direct and all
# Because functions are copied between contracts, two functions can have the same ref
# So we need to first look with respect to the direct context
ret = _find_variable_from_ref_declaration(
referenced_declaration, direct_contracts, direct_functions_parser
)
if ret:
return ret, False
function_parser: Optional[FunctionSolc] = (
caller_context if isinstance(caller_context, FunctionSolc) else None
)
ret = _find_variable_in_function_parser(var_name, function_parser, referenced_declaration)
if ret:
return ret, False
contract: Optional[Contract] = None
contract_declarer: Optional[Contract] = None
if isinstance(caller_context, ContractSolc):
contract = caller_context.underlying_contract
contract_declarer = caller_context.underlying_contract
elif isinstance(caller_context, FunctionSolc):
underlying_func = caller_context.underlying_function
if isinstance(underlying_func, FunctionContract):
contract = underlying_func.contract
contract_declarer = underlying_func.contract_declarer
else:
assert isinstance(underlying_func, FunctionTopLevel)
ret = _find_in_contract(var_name, contract, contract_declarer, is_super)
if ret:
return ret, False
# Could refer to any enum
all_enumss = [c.enums_as_dict for c in sl.contracts]
all_enums = {k: v for d in all_enumss for k, v in d.items()}
if var_name in all_enums:
return all_enums[var_name], False
contracts = sl.contracts_as_dict
if var_name in contracts:
return contracts[var_name], False
if var_name in SOLIDITY_VARIABLES:
return SolidityVariable(var_name), False
if var_name in SOLIDITY_FUNCTIONS:
return SolidityFunction(var_name), False
# Top level must be at the end, if nothing else was found
ret, var_was_created = _find_top_level(var_name, sl)
if ret:
return ret, var_was_created
# Look from reference declaration in all the contracts at the end
# Because they are many instances where this can't be trusted
# For example in
# contract A{
# function _f() internal view returns(uint){
# return 1;
# }
#
# function get() public view returns(uint){
# return _f();
# }
# }
#
# contract B is A{
# function _f() internal view returns(uint){
# return 2;
# }
#
# }
# get's AST will say that the ref declaration for _f() is A._f(), but in the context of B, its not
ret = _find_variable_from_ref_declaration(
referenced_declaration, all_contracts, all_functions_parser
)
if ret:
return ret, False
raise VariableNotFound("Variable not found: {} (context {})".format(var_name, contract))

@ -2,11 +2,13 @@ import json
import logging
import os
import re
from pathlib import Path
from typing import List, Dict
from slither.analyses.data_dependency.data_dependency import compute_dependency
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.declarations import Contract
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.import_directive import Import
@ -15,6 +17,7 @@ from slither.core.declarations.structure_top_level import StructureTopLevel
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.exceptions import SlitherException
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc
from slither.solc_parsing.exceptions import VariableNotFound
@ -36,6 +39,7 @@ class SlitherCompilationUnitSolc:
self._underlying_contract_to_parser: Dict[Contract, ContractSolc] = dict()
self._structures_top_level_parser: List[StructureTopLevelSolc] = []
self._custom_error_parser: List[CustomErrorSolc] = []
self._variables_top_level_parser: List[TopLevelVariableSolc] = []
self._functions_top_level_parser: List[FunctionSolc] = []
@ -145,7 +149,7 @@ class SlitherCompilationUnitSolc:
def parse_top_level_from_loaded_json(
self, data_loaded: Dict, filename: str
): # pylint: disable=too-many-branches,too-many-statements
): # pylint: disable=too-many-branches,too-many-statements,too-many-locals
if "nodeType" in data_loaded:
self._is_compact_ast = True
@ -163,6 +167,8 @@ class SlitherCompilationUnitSolc:
logger.error("solc version is not supported")
return
if self.get_children() not in data_loaded:
return
for top_level_data in data_loaded[self.get_children()]:
if top_level_data[self.get_key()] == "ContractDefinition":
contract = Contract(self._compilation_unit)
@ -181,12 +187,28 @@ class SlitherCompilationUnitSolc:
self._compilation_unit.pragma_directives.append(pragma)
elif top_level_data[self.get_key()] == "ImportDirective":
if self.is_compact_ast:
import_directive = Import(top_level_data["absolutePath"])
import_directive = Import(
Path(
self._compilation_unit.crytic_compile.working_dir,
top_level_data["absolutePath"],
)
)
# TODO investigate unitAlias in version < 0.7 and legacy ast
if "unitAlias" in top_level_data:
import_directive.alias = top_level_data["unitAlias"]
else:
import_directive = Import(top_level_data["attributes"].get("absolutePath", ""))
import_directive = Import(
Path(
self._compilation_unit.crytic_compile.working_dir,
top_level_data["attributes"].get("absolutePath", ""),
)
)
# TODO investigate unitAlias in version < 0.7 and legacy ast
if (
"attributes" in top_level_data
and "unitAlias" in top_level_data["attributes"]
):
import_directive.alias = top_level_data["attributes"]["unitAlias"]
import_directive.set_offset(top_level_data["src"], self._compilation_unit)
self._compilation_unit.import_directives.append(import_directive)
@ -211,12 +233,21 @@ class SlitherCompilationUnitSolc:
self._variables_top_level_parser.append(var_parser)
elif top_level_data[self.get_key()] == "FunctionDefinition":
func = FunctionTopLevel(self._compilation_unit)
func.set_offset(top_level_data["src"], self._compilation_unit)
func_parser = FunctionSolc(func, top_level_data, None, self)
self._compilation_unit.functions_top_level.append(func)
self._functions_top_level_parser.append(func_parser)
self.add_function_or_modifier_parser(func_parser)
elif top_level_data[self.get_key()] == "ErrorDefinition":
custom_error = CustomErrorTopLevel(self._compilation_unit)
custom_error.set_offset(top_level_data["src"], self._compilation_unit)
custom_error_parser = CustomErrorSolc(custom_error, top_level_data, self)
self._compilation_unit.custom_errors.append(custom_error)
self._custom_error_parser.append(custom_error_parser)
else:
raise SlitherException(f"Top level {top_level_data[self.get_key()]} not supported")
@ -504,6 +535,7 @@ Please rename it, this name is reserved for Slither's internals"""
contract.parse_state_variables()
contract.parse_modifiers()
contract.parse_functions()
contract.parse_custom_errors()
contract.set_is_analyzed(True)
def _analyze_struct_events(self, contract: ContractSolc):
@ -516,6 +548,7 @@ Please rename it, this name is reserved for Slither's internals"""
contract.analyze_events()
contract.analyze_using_for()
contract.analyze_custom_errors()
contract.set_is_analyzed(True)
@ -538,6 +571,10 @@ Please rename it, this name is reserved for Slither's internals"""
func_parser.analyze_params()
self._compilation_unit.add_function(func_parser.underlying_function)
def _analyze_params_custom_error(self):
for custom_error_parser in self._custom_error_parser:
custom_error_parser.analyze_params()
def _analyze_content_top_level_function(self):
try:
for func_parser in self._functions_top_level_parser:
@ -551,6 +588,7 @@ Please rename it, this name is reserved for Slither's internals"""
contract.analyze_params_modifiers()
contract.analyze_params_functions()
self._analyze_params_top_level_function()
self._analyze_params_custom_error()
contract.analyze_state_variables()

@ -190,7 +190,12 @@ def _find_from_type_name( # pylint: disable=too-many-locals,too-many-branches,t
return UserDefinedType(var_type)
def parse_type(t: Union[Dict, UnknownType], caller_context):
def parse_type(
t: Union[Dict, UnknownType],
caller_context: Union[
"SlitherCompilationUnitSolc", "FunctionSolc", "ContractSolc", "CustomSolc"
],
):
# local import to avoid circular dependency
# pylint: disable=too-many-locals,too-many-branches,too-many-statements
# pylint: disable=import-outside-toplevel
@ -198,17 +203,22 @@ def parse_type(t: Union[Dict, UnknownType], caller_context):
from slither.solc_parsing.variables.function_type_variable import FunctionTypeVariableSolc
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
sl: "SlitherCompilationUnit"
# Note: for convenicence top level functions use the same parser than function in contract
# but contract_parser is set to None
if isinstance(caller_context, SlitherCompilationUnitSolc) or (
if isinstance(caller_context, (SlitherCompilationUnitSolc, CustomErrorSolc)) or (
isinstance(caller_context, FunctionSolc) and caller_context.contract_parser is None
):
structures_direct_access: List["Structure"]
if isinstance(caller_context, SlitherCompilationUnitSolc):
sl = caller_context.compilation_unit
next_context = caller_context
elif isinstance(caller_context, CustomErrorSolc):
sl = caller_context.underlying_custom_error.compilation_unit
next_context = caller_context.slither_parser
else:
assert isinstance(caller_context, FunctionSolc)
sl = caller_context.underlying_function.compilation_unit
@ -235,13 +245,13 @@ def parse_type(t: Union[Dict, UnknownType], caller_context):
contract = caller_context.underlying_contract
next_context = caller_context
structures_direct_access = (
contract.structures + contract.compilation_unit.structures_top_level
)
structures_direct_access = contract.structures
structures_direct_access += contract.compilation_unit.structures_top_level
all_structuress = [c.structures for c in contract.compilation_unit.contracts]
all_structures = [item for sublist in all_structuress for item in sublist]
all_structures += contract.compilation_unit.structures_top_level
enums_direct_access = contract.enums + contract.compilation_unit.enums_top_level
enums_direct_access: List["Enum"] = contract.enums
enums_direct_access += contract.compilation_unit.enums_top_level
all_enumss = [c.enums for c in contract.compilation_unit.contracts]
all_enums = [item for sublist in all_enumss for item in sublist]
all_enums += contract.compilation_unit.enums_top_level

@ -136,6 +136,14 @@ class VariableDeclarationSolc:
if "constant" in attributes:
self._variable.is_constant = attributes["constant"]
if "mutability" in attributes:
# Note: this checked is not needed if "constant" was already in attribute, but we keep it
# for completion
if attributes["mutability"] == "constant":
self._variable.is_constant = True
if attributes["mutability"] == "immutable":
self._variable.is_immutable = True
self._analyze_variable_attributes(attributes)
if self._is_compact_ast:

@ -10,7 +10,9 @@ from slither.core.declarations import (
SolidityFunction,
Contract,
)
from slither.core.declarations.function import FunctionLanguage
from slither.core.declarations.function_contract import FunctionContract
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.expressions import (
Literal,
AssignmentOperation,
@ -137,7 +139,7 @@ class YulScope(metaclass=abc.ABCMeta):
@property
def compilation_unit(self) -> SlitherCompilationUnit:
return self._contract.compilation_unit
return self._parent_func.compilation_unit
@property
def parent_func(self) -> Optional[Function]:
@ -353,7 +355,13 @@ def convert_yul_block(
def convert_yul_function_definition(
root: YulScope, parent: YulNode, ast: Dict, node_scope: Union[Function, Scope]
) -> YulNode:
func = FunctionContract(root.compilation_unit)
top_node_scope = node_scope
while not isinstance(top_node_scope, Function):
top_node_scope = top_node_scope.father
assert isinstance(top_node_scope, (FunctionTopLevel, FunctionContract))
func = type(top_node_scope)(root.compilation_unit)
func.function_language = FunctionLanguage.Yul
yul_function = YulFunction(func, root, ast, node_scope)
root.contract.add_function(func)
@ -707,6 +715,15 @@ def parse_yul_function_call(root: YulScope, node: YulNode, ast: Dict) -> Optiona
raise SlitherException(f"unexpected function call target type {str(type(ident.value))}")
def _check_for_state_variable_name(root: YulScope, potential_name: str) -> Optional[Identifier]:
root_function = root.function
if isinstance(root_function, FunctionContract):
var = root_function.contract.get_state_variable_from_name(potential_name)
if var:
return Identifier(var)
return None
def parse_yul_identifier(root: YulScope, _node: YulNode, ast: Dict) -> Optional[Expression]:
name = ast["name"]
@ -714,14 +731,16 @@ def parse_yul_identifier(root: YulScope, _node: YulNode, ast: Dict) -> Optional[
return Identifier(YulBuiltin(name))
# check function-scoped variables
if root.parent_func:
variable = root.parent_func.get_local_variable_from_name(name)
parent_func = root.parent_func
if parent_func:
variable = parent_func.get_local_variable_from_name(name)
if variable:
return Identifier(variable)
variable = root.parent_func.contract.get_state_variable_from_name(name)
if variable:
return Identifier(variable)
if isinstance(parent_func, FunctionContract):
variable = parent_func.contract.get_state_variable_from_name(name)
if variable:
return Identifier(variable)
# check yul-scoped variable
variable = root.get_yul_local_variable_from_name(name)
@ -737,17 +756,17 @@ def parse_yul_identifier(root: YulScope, _node: YulNode, ast: Dict) -> Optional[
# check for magic suffixes
if name.endswith("_slot") or name.endswith(".slot"):
potential_name = name[:-5]
var = root.function.contract.get_state_variable_from_name(potential_name)
if var:
return Identifier(var)
variable_found = _check_for_state_variable_name(root, potential_name)
if variable_found:
return variable_found
var = root.function.get_local_variable_from_name(potential_name)
if var and var.is_storage:
return Identifier(var)
if name.endswith("_offset") or name.endswith(".offset"):
potential_name = name[:-7]
var = root.function.contract.get_state_variable_from_name(potential_name)
if var:
return Identifier(var)
variable_found = _check_for_state_variable_name(root, potential_name)
if variable_found:
return variable_found
raise SlitherException(f"unresolved reference to identifier {name}")

@ -88,7 +88,7 @@ class Flattening:
:return:
"""
src_mapping = contract.source_mapping
content = self._slither.source_code[src_mapping["filename_absolute"]].encode("utf8")
content = self._slither.source_code[src_mapping["filename_absolute"]]
start = src_mapping["start"]
end = src_mapping["start"] + src_mapping["length"]
@ -171,7 +171,7 @@ class Flattening:
index = index - start
if patch_type == "public_to_external":
content = content[:index] + "public" + content[index + len("external") :]
if patch_type == "private_to_internal":
elif patch_type == "private_to_internal":
content = content[:index] + "internal" + content[index + len("private") :]
elif patch_type == "calldata_to_memory":
content = content[:index] + "memory" + content[index + len("calldata") :]
@ -179,7 +179,7 @@ class Flattening:
assert patch_type == "line_removal"
content = content[:index] + " // " + content[index:]
self._source_codes[contract] = content.decode("utf8")
self._source_codes[contract] = content
def _pragmas(self) -> str:
"""

@ -1,5 +1,6 @@
import re
import logging
from typing import Set, Tuple
from slither.core.declarations import Function
from slither.core.variables.variable import Variable
@ -13,13 +14,13 @@ logger = logging.getLogger("Slither.kspec")
# pylint: disable=anomalous-backslash-in-string
def _refactor_type(targeted_type):
def _refactor_type(targeted_type: str) -> str:
return {"uint": "uint256", "int": "int256"}.get(targeted_type, targeted_type)
def _get_all_covered_kspec_functions(target):
def _get_all_covered_kspec_functions(target: str) -> Set[Tuple[str, str]]:
# Create a set of our discovered functions which are covered
covered_functions = set()
covered_functions: Set[Tuple[str, str]] = set()
BEHAVIOUR_PATTERN = re.compile("behaviour\s+(\S+)\s+of\s+(\S+)")
INTERFACE_PATTERN = re.compile("interface\s+([^\r\n]+)")

@ -69,14 +69,14 @@ class AbstractMutator(metaclass=abc.ABCMeta): # pylint: disable=too-few-public-
"""TODO Documentation"""
return dict()
def mutate(self):
patches = self._mutate()
def mutate(self) -> None:
all_patches = self._mutate()
for file in patches["patches"]:
for file in all_patches["patches"]:
original_txt = self.slither.source_code[file].encode("utf8")
patched_txt = original_txt
offset = 0
patches = patches["patches"][file]
patches = all_patches["patches"][file]
patches.sort(key=lambda x: x["start"])
if not all(patches[i]["end"] <= patches[i + 1]["end"] for i in range(len(patches) - 1)):
logger.info(f"Impossible to generate patch; patches collisions: {patches}")

@ -1,7 +1,8 @@
import argparse
import sys
import logging
from argparse import ArgumentParser, Namespace
from crytic_compile import cryticparser
from slither import Slither
from slither.utils.colors import red
@ -15,12 +16,12 @@ logging.basicConfig()
logging.getLogger("Slither").setLevel(logging.INFO)
def parse_args():
def parse_args() -> Namespace:
"""
Parse the underlying arguments for the program.
:return: Returns the arguments for the program.
"""
parser = argparse.ArgumentParser(
parser: ArgumentParser = ArgumentParser(
description="PossiblePaths",
usage="possible_paths.py filename [contract.function targets]",
)
@ -36,7 +37,7 @@ def parse_args():
return parser.parse_args()
def main():
def main() -> None:
# ------------------------------
# PossiblePaths.py
# Usage: python3 possible_paths.py filename targets

@ -20,7 +20,7 @@ from slither.tools.upgradeability.utils.command_line import (
)
logging.basicConfig()
logger = logging.getLogger("Slither")
logger: logging.Logger = logging.getLogger("Slither")
logger.setLevel(logging.INFO)

@ -1,9 +1,11 @@
import abc
from typing import Optional
from logging import Logger
from typing import Optional, List, Dict, Union, Callable
from slither.core.declarations import Contract
from slither.utils.colors import green, yellow, red
from slither.utils.comparable_enum import ComparableEnum
from slither.utils.output import Output
from slither.utils.output import Output, SupportedOutput
class IncorrectCheckInitialization(Exception):
@ -15,9 +17,10 @@ class CheckClassification(ComparableEnum):
MEDIUM = 1
LOW = 2
INFORMATIONAL = 3
UNIMPLEMENTED = 999
classification_colors = {
classification_colors: Dict[CheckClassification, Callable[[str], str]] = {
CheckClassification.INFORMATIONAL: green,
CheckClassification.LOW: yellow,
CheckClassification.MEDIUM: yellow,
@ -35,7 +38,7 @@ classification_txt = {
class AbstractCheck(metaclass=abc.ABCMeta):
ARGUMENT = ""
HELP = ""
IMPACT: Optional[CheckClassification] = None
IMPACT: CheckClassification = CheckClassification.UNIMPLEMENTED
WIKI = ""
@ -48,7 +51,13 @@ class AbstractCheck(metaclass=abc.ABCMeta):
REQUIRE_PROXY = False
REQUIRE_CONTRACT_V2 = False
def __init__(self, logger, contract, proxy=None, contract_v2=None):
def __init__(
self,
logger: Logger,
contract: Contract,
proxy: Optional[Contract] = None,
contract_v2: Optional[Contract] = None,
) -> None:
self.logger = logger
self.contract = contract
self.proxy = proxy
@ -120,14 +129,14 @@ class AbstractCheck(metaclass=abc.ABCMeta):
)
@abc.abstractmethod
def _check(self):
def _check(self) -> List[Output]:
"""TODO Documentation"""
return []
def check(self):
all_results = self._check()
def check(self) -> List[Dict]:
all_outputs = self._check()
# Keep only dictionaries
all_results = [r.data for r in all_results]
all_results = [r.data for r in all_outputs]
if all_results:
if self.logger:
info = "\n"
@ -137,7 +146,11 @@ class AbstractCheck(metaclass=abc.ABCMeta):
self._log(info)
return all_results
def generate_result(self, info, additional_fields=None):
def generate_result(
self,
info: Union[str, List[Union[str, SupportedOutput]]],
additional_fields: Optional[Dict] = None,
) -> Output:
output = Output(
info, additional_fields, markdown_root=self.contract.compilation_unit.core.markdown_root
)
@ -146,10 +159,10 @@ class AbstractCheck(metaclass=abc.ABCMeta):
return output
def _log(self, info):
def _log(self, info: str) -> None:
if self.logger:
self.logger.info(self.color(info))
@property
def color(self):
def color(self) -> Callable[[str], str]:
return classification_colors[self.IMPACT]

@ -34,6 +34,7 @@ defaults_flag_in_config = {
"exclude_medium": False,
"exclude_high": False,
"json": None,
"sarif": None,
"json-types": ",".join(DEFAULT_JSON_OUTPUT_TYPES),
"disable_color": False,
"filter_paths": None,

@ -8,7 +8,7 @@ class MyPrettyTable:
self._field_names = field_names
self._rows: List = []
def add_row(self, row):
def add_row(self, row: List[str]) -> None:
self._rows.append(row)
def to_pretty_table(self) -> PrettyTable:
@ -20,5 +20,5 @@ class MyPrettyTable:
def to_json(self) -> Dict:
return {"fields_names": self._field_names, "rows": self._rows}
def __str__(self):
def __str__(self) -> str:
return str(self.to_pretty_table())

@ -6,6 +6,7 @@ import zipfile
from collections import OrderedDict
from typing import Optional, Dict, List, Union, Any, TYPE_CHECKING
from zipfile import ZipFile
from pkg_resources import require
from slither.core.cfg.node import Node
from slither.core.declarations import Contract, Function, Enum, Event, Structure, Pragma
@ -17,6 +18,7 @@ from slither.utils.myprettytable import MyPrettyTable
if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.detectors.abstract_detector import AbstractDetector
logger = logging.getLogger("Slither")
@ -28,7 +30,7 @@ logger = logging.getLogger("Slither")
###################################################################################
def output_to_json(filename: str, error, results: Dict):
def output_to_json(filename: Optional[str], error, results: Dict) -> None:
"""
:param filename: Filename where the json will be written. If None or "-", write to stdout
@ -56,6 +58,127 @@ def output_to_json(filename: str, error, results: Dict):
json.dump(json_result, f, indent=2)
def _output_result_to_sarif(
detector: Dict, detectors_classes: List["AbstractDetector"], sarif: Dict
) -> None:
confidence = "very-high"
if detector["confidence"] == "Medium":
confidence = "high"
elif detector["confidence"] == "Low":
confidence = "medium"
elif detector["confidence"] == "Informational":
confidence = "low"
risk = "0.0"
if detector["impact"] == "High":
risk = "8.0"
elif detector["impact"] == "Medium":
risk = "4.0"
elif detector["impact"] == "Low":
risk = "3.0"
detector_class = next((d for d in detectors_classes if d.ARGUMENT == detector["check"]))
check_id = (
str(detector_class.IMPACT.value)
+ "-"
+ str(detector_class.CONFIDENCE.value)
+ "-"
+ detector["check"]
)
rule = {
"id": check_id,
"name": detector["check"],
"properties": {"precision": confidence, "security-severity": risk},
"shortDescription": {"text": detector_class.WIKI_TITLE},
"help": {"text": detector_class.WIKI_RECOMMENDATION},
}
# Add the rule if does not exist yet
if len([x for x in sarif["runs"][0]["tool"]["driver"]["rules"] if x["id"] == check_id]) == 0:
sarif["runs"][0]["tool"]["driver"]["rules"].append(rule)
if not detector["elements"]:
logger.info(yellow("Cannot generate Github security alert for finding without location"))
logger.info(yellow(detector["description"]))
logger.info(yellow("This will be supported in a future Slither release"))
return
# From 3.19.10 (http://docs.oasis-open.org/sarif/sarif/v2.0/csprd01/sarif-v2.0-csprd01.html)
# The locations array SHALL NOT contain more than one element unless the condition indicated by the result,
# if any, can only be corrected by making a change at every location specified in the array.
finding = detector["elements"][0]
path = finding["source_mapping"]["filename_relative"]
start_line = finding["source_mapping"]["lines"][0]
end_line = finding["source_mapping"]["lines"][-1]
sarif["runs"][0]["results"].append(
{
"ruleId": check_id,
"message": {"text": detector["description"], "markdown": detector["markdown"]},
"level": "warning",
"locations": [
{
"physicalLocation": {
"artifactLocation": {"uri": path},
"region": {"startLine": start_line, "endLine": end_line},
}
}
],
"partialFingerprints": {"id": detector["id"]},
}
)
def output_to_sarif(
filename: Optional[str], results: Dict, detectors_classes: List["AbstractDetector"]
) -> None:
"""
:param filename:
:type filename:
:param results:
:type results:
:return:
:rtype:
"""
sarif: Dict[str, Any] = {
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "Slither",
"informationUri": "https://github.com/crytic/slither",
"version": require("slither-analyzer")[0].version,
"rules": [],
}
},
"results": [],
}
],
}
for detector in results["detectors"]:
_output_result_to_sarif(detector, detectors_classes, sarif)
if filename == "-":
filename = None
# Determine if we should output to stdout
if filename is None:
# Write json to console
print(json.dumps(sarif))
else:
# Write json to file
if os.path.isfile(filename):
logger.info(yellow(f"{filename} exists already, the overwrite is prevented"))
else:
with open(filename, "w", encoding="utf8") as f:
json.dump(sarif, f, indent=2)
# https://docs.python.org/3/library/zipfile.html#zipfile-objects
ZIP_TYPES_ACCEPTED = {
"lzma": zipfile.ZIP_LZMA,
@ -215,6 +338,7 @@ def _create_parent_element(element):
SupportedOutput = Union[Variable, Contract, Function, Enum, Event, Structure, Pragma, Node]
AllSupportedOutput = Union[str, SupportedOutput]
class Output:

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save