Merge pull request #2407 from crytic/dev

sync master<> dev
pull/2409/head 0.10.2
alpharush 8 months ago committed by GitHub
commit fdf54f624d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 0
      .github/scripts/integration_test_runner.sh
  2. 0
      .github/scripts/tool_test_runner.sh
  3. 0
      .github/scripts/unit_test_runner.sh
  4. 4
      .github/workflows/ci.yml
  5. 2
      .github/workflows/docs.yml
  6. 2
      .github/workflows/doctor.yml
  7. 40
      .github/workflows/issue-metrics.yml
  8. 8
      .github/workflows/linter.yml
  9. 2
      .github/workflows/publish.yml
  10. 2
      .github/workflows/pylint.yml
  11. 12
      .github/workflows/test.yml
  12. 8
      CONTRIBUTING.md
  13. 37
      README.md
  14. 4
      pyproject.toml
  15. 0
      scripts/ci_test_interface.sh
  16. 28
      scripts/update_buggy_versions.py
  17. 6
      setup.py
  18. 23
      slither/__main__.py
  19. 56
      slither/core/declarations/contract.py
  20. 66
      slither/core/declarations/function.py
  21. 5
      slither/core/declarations/function_contract.py
  22. 17
      slither/core/declarations/function_top_level.py
  23. 2
      slither/core/declarations/import_directive.py
  24. 3
      slither/core/declarations/solidity_variables.py
  25. 9
      slither/core/declarations/using_for_top_level.py
  26. 49
      slither/core/scope/scope.py
  27. 55
      slither/core/slither_core.py
  28. 2
      slither/core/solidity_types/array_type.py
  29. 1
      slither/detectors/all_detectors.py
  30. 26
      slither/detectors/attributes/constant_pragma.py
  31. 92
      slither/detectors/attributes/incorrect_solc.py
  32. 2
      slither/detectors/functions/modifier.py
  33. 2
      slither/detectors/operations/unchecked_send_return_value.py
  34. 2
      slither/detectors/operations/unchecked_transfer.py
  35. 6
      slither/detectors/operations/unused_return_values.py
  36. 4
      slither/detectors/slither/name_reused.py
  37. 2
      slither/detectors/statements/too_many_digits.py
  38. 75
      slither/detectors/statements/unused_import.py
  39. 2
      slither/formatters/naming_convention/naming_convention.py
  40. 2
      slither/printers/abstract_printer.py
  41. 7
      slither/printers/inheritance/inheritance_graph.py
  42. 12
      slither/printers/summary/declaration.py
  43. 71
      slither/slither.py
  44. 97
      slither/slithir/convert.py
  45. 3
      slither/slithir/operations/call.py
  46. 1
      slither/slithir/operations/high_level_call.py
  47. 9
      slither/slithir/operations/init_array.py
  48. 4
      slither/slithir/operations/new_array.py
  49. 15
      slither/slithir/operations/new_contract.py
  50. 6
      slither/slithir/utils/ssa.py
  51. 30
      slither/solc_parsing/declarations/contract.py
  52. 20
      slither/solc_parsing/declarations/event_contract.py
  53. 75
      slither/solc_parsing/declarations/event_top_level.py
  54. 256
      slither/solc_parsing/declarations/function.py
  55. 17
      slither/solc_parsing/expressions/expression_parsing.py
  56. 15
      slither/solc_parsing/expressions/find_variable.py
  57. 93
      slither/solc_parsing/slither_compilation_unit_solc.py
  58. 35
      slither/solc_parsing/solidity_types/type_parsing.py
  59. 4
      slither/solc_parsing/yul/parse_yul.py
  60. 20
      slither/tools/mutator/README.md
  61. 258
      slither/tools/mutator/__main__.py
  62. 2
      slither/tools/mutator/mutators/RR.py
  63. 95
      slither/tools/mutator/mutators/abstract_mutator.py
  64. 109
      slither/tools/mutator/utils/file_handling.py
  65. 120
      slither/tools/mutator/utils/testing_generated_mutant.py
  66. 1657
      slither/utils/buggy_versions.py
  67. 4
      slither/utils/output.py
  68. 49
      slither/utils/source_mapping.py
  69. 3
      slither/utils/upgradeability.py
  70. 17
      slither/utils/using_for.py
  71. 22
      slither/visitors/slithir/expression_to_slithir.py
  72. 15
      slither/vyper_parsing/expressions/expression_parsing.py
  73. 4
      tests/conftest.py
  74. 9
      tests/e2e/detectors/snapshots/detectors__detector_ConstantPragma_0_4_25_pragma_0_4_25_sol__0.txt
  75. 9
      tests/e2e/detectors/snapshots/detectors__detector_ConstantPragma_0_5_16_pragma_0_5_16_sol__0.txt
  76. 9
      tests/e2e/detectors/snapshots/detectors__detector_ConstantPragma_0_6_11_pragma_0_6_11_sol__0.txt
  77. 9
      tests/e2e/detectors/snapshots/detectors__detector_ConstantPragma_0_7_6_pragma_0_7_6_sol__0.txt
  78. 21
      tests/e2e/detectors/snapshots/detectors__detector_IncorrectSolc_0_4_25_static_sol__0.txt
  79. 21
      tests/e2e/detectors/snapshots/detectors__detector_IncorrectSolc_0_5_14_static_sol__0.txt
  80. 19
      tests/e2e/detectors/snapshots/detectors__detector_IncorrectSolc_0_5_16_dynamic_1_sol__0.txt
  81. 21
      tests/e2e/detectors/snapshots/detectors__detector_IncorrectSolc_0_5_16_dynamic_2_sol__0.txt
  82. 18
      tests/e2e/detectors/snapshots/detectors__detector_IncorrectSolc_0_5_16_static_sol__0.txt
  83. 17
      tests/e2e/detectors/snapshots/detectors__detector_IncorrectSolc_0_6_10_static_sol__0.txt
  84. 17
      tests/e2e/detectors/snapshots/detectors__detector_IncorrectSolc_0_6_11_dynamic_1_sol__0.txt
  85. 19
      tests/e2e/detectors/snapshots/detectors__detector_IncorrectSolc_0_6_11_dynamic_2_sol__0.txt
  86. 17
      tests/e2e/detectors/snapshots/detectors__detector_IncorrectSolc_0_6_11_static_sol__0.txt
  87. 15
      tests/e2e/detectors/snapshots/detectors__detector_IncorrectSolc_0_7_4_static_sol__0.txt
  88. 15
      tests/e2e/detectors/snapshots/detectors__detector_IncorrectSolc_0_7_6_dynamic_1_sol__0.txt
  89. 6
      tests/e2e/detectors/snapshots/detectors__detector_IncorrectSolc_0_7_6_dynamic_2_sol__0.txt
  90. 15
      tests/e2e/detectors/snapshots/detectors__detector_IncorrectSolc_0_7_6_static_sol__0.txt
  91. 3
      tests/e2e/detectors/snapshots/detectors__detector_ModifierDefaultDetection_0_4_25_modifier_default_sol__0.txt
  92. 3
      tests/e2e/detectors/snapshots/detectors__detector_ModifierDefaultDetection_0_5_16_modifier_default_sol__0.txt
  93. 3
      tests/e2e/detectors/snapshots/detectors__detector_ModifierDefaultDetection_0_6_11_modifier_default_sol__0.txt
  94. 3
      tests/e2e/detectors/snapshots/detectors__detector_ModifierDefaultDetection_0_7_6_modifier_default_sol__0.txt
  95. 20
      tests/e2e/detectors/snapshots/detectors__detector_ReentrancyBenign_0_5_16_reentrancy_benign_sol__0.txt
  96. 32
      tests/e2e/detectors/snapshots/detectors__detector_ReentrancyBenign_0_6_11_reentrancy_benign_sol__0.txt
  97. 36
      tests/e2e/detectors/snapshots/detectors__detector_ReentrancyBenign_0_7_6_reentrancy_benign_sol__0.txt
  98. 6
      tests/e2e/detectors/snapshots/detectors__detector_ReentrancyReadBeforeWritten_0_5_16_reentrancy_write_sol__0.txt
  99. 18
      tests/e2e/detectors/snapshots/detectors__detector_ReentrancyReadBeforeWritten_0_6_11_reentrancy_write_sol__0.txt
  100. 6
      tests/e2e/detectors/snapshots/detectors__detector_ReentrancyReadBeforeWritten_0_7_6_reentrancy_write_sol__0.txt
  101. Some files were not shown because too many files have changed in this diff Show More

@ -26,7 +26,7 @@ jobs:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-2022"]
python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.11"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11"]') }}
python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.12"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11", "3.12"]') }}
type: ["cli",
"dapp",
"data_dependency",
@ -67,7 +67,7 @@ jobs:
- name: Set up nix
if: matrix.type == 'dapp'
uses: cachix/install-nix-action@v25
uses: cachix/install-nix-action@v26
- name: Set up cachix
if: matrix.type == 'dapp'

@ -30,7 +30,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v4
uses: actions/configure-pages@v5
- uses: actions/setup-python@v5
with:
python-version: '3.8'

@ -23,7 +23,7 @@ jobs:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-2022"]
python: ["3.8", "3.9", "3.10", "3.11"]
python: ["3.8", "3.9", "3.10", "3.11", "3.12"]
exclude:
# strange failure
- os: windows-2022

@ -0,0 +1,40 @@
name: Monthly issue metrics
on:
workflow_dispatch:
schedule:
- cron: '3 2 1 * *'
permissions:
issues: write
pull-requests: read
jobs:
build:
name: issue metrics
runs-on: ubuntu-latest
steps:
- name: Get dates for last month
shell: bash
run: |
# Calculate the first day of the previous month
first_day=$(date -d "last month" +%Y-%m-01)
# Calculate the last day of the previous month
last_day=$(date -d "$first_day +1 month -1 day" +%Y-%m-%d)
#Set an environment variable with the date range
echo "$first_day..$last_day"
echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV"
- name: Run issue-metrics tool
uses: github/issue-metrics@v3
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SEARCH_QUERY: 'repo:crytic/slither is:issue created:${{ env.last_month }} -reason:"not planned" -reason:"duplicate"'
- name: Create issue
uses: peter-evans/create-issue-from-file@v5
with:
title: Monthly issue metrics report
token: ${{ secrets.GITHUB_TOKEN }}
content-filepath: ./issue_metrics.md

@ -45,7 +45,7 @@ jobs:
echo "::add-matcher::.github/workflows/matchers/yamllint.json"
- name: Lint everything else
uses: super-linter/super-linter/slim@v4.9.2
uses: super-linter/super-linter/slim@v6.1.1
if: always()
env:
# run linter on everything to catch preexisting problems
@ -58,6 +58,7 @@ jobs:
VALIDATE_PYTHON_BLACK: false
VALIDATE_PYTHON_ISORT: false
VALIDATE_JSON: false
VALIDATE_JAVASCRIPT_ES: false
VALIDATE_JAVASCRIPT_STANDARD: false
VALIDATE_PYTHON_FLAKE8: false
VALIDATE_DOCKERFILE: false
@ -65,7 +66,8 @@ jobs:
VALIDATE_EDITORCONFIG: false
VALIDATE_JSCPD: false
VALIDATE_PYTHON_MYPY: false
# Until we upgrade the super linter for actionlintÒ
VALIDATE_GITHUB_ACTIONS: false
VALIDATE_CHECKOV: false
# TODO: consider enabling
VALIDATE_SHELL_SHFMT: false
SHELLCHECK_OPTS: "-e SC1090"
FILTER_REGEX_EXCLUDE: .*tests/.*.(json|zip|sol)

@ -44,7 +44,7 @@ jobs:
path: dist/
- name: publish
uses: pypa/gh-action-pypi-publish@v1.8.11
uses: pypa/gh-action-pypi-publish@v1.8.14
- name: sign
uses: sigstore/gh-action-sigstore-python@v2.1.1

@ -43,7 +43,7 @@ jobs:
echo "::add-matcher::.github/workflows/matchers/pylint.json"
- name: Pylint
uses: super-linter/super-linter/slim@v4.9.2
uses: super-linter/super-linter/slim@v6.1.1
if: always()
env:
# Run linters only on new files for pylint to speed up the CI

@ -25,7 +25,7 @@ jobs:
matrix:
os: ["ubuntu-latest", "windows-2022"]
type: ["unit", "integration", "tool"]
python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.11"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11"]') }}
python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.12"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11", "3.12"]') }}
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }}
@ -80,11 +80,11 @@ jobs:
# Only run coverage on ubuntu-latest.
run: |
if [ ${{ matrix.os }} = "ubuntu-latest" ]; then
TEST_ARGS="--cov=slither --cov-append"
TEST_ARGS=(--cov=slither --cov-append)
elif [ ${{ matrix.os }} = "windows-2022" ]; then
TEST_ARGS=""
TEST_ARGS=()
fi
bash "./.github/scripts/${TEST_TYPE}_test_runner.sh" $TEST_ARGS
bash "./.github/scripts/${TEST_TYPE}_test_runner.sh" "${TEST_ARGS[@]}"
- name: Upload coverage
@ -119,5 +119,5 @@ jobs:
run: |
set +e
python -m coverage combine
echo "## python coverage" >> $GITHUB_STEP_SUMMARY
python -m coverage report -m --format=markdown >> $GITHUB_STEP_SUMMARY
echo "## python coverage" >> "$GITHUB_STEP_SUMMARY"
python -m coverage report -m --format=markdown >> "$GITHUB_STEP_SUMMARY"

@ -6,7 +6,7 @@ If you're unsure where to start, we recommend our [`good first issue`](https://g
## Bug reports and feature suggestions
Bug reports and feature suggestions can be submitted to our issue tracker. For bug reports, attaching the contract that caused the bug will help us in debugging and resolving the issue quickly. If you find a security vulnerability, do not open an issue; email opensource@trailofbits.com instead.
Bug reports and feature suggestions can be submitted to our issue tracker. For bug reports, attaching the contract that caused the bug will help us in debugging and resolving the issue quickly. If you find a security vulnerability, do not open an issue; email <opensource@trailofbits.com> instead.
## Questions
@ -14,7 +14,7 @@ Questions can be submitted to the "Discussions" page, and you may also join our
## Code
Slither uses the pull request contribution model. Please make an account on Github, fork this repo, and submit code contributions via pull request. For more documentation, look [here](https://guides.github.com/activities/forking/).
Slither uses the pull request contribution model. Please make an account on GitHub, fork this repository, and submit code contributions via pull request. For more documentation, look [here](https://guides.github.com/activities/forking/).
Some pull request guidelines:
@ -63,7 +63,7 @@ To automatically reformat the code:
- `make reformat`
We use pylint `2.13.4`, black `22.3.0`.
We use pylint `3.0.3`, black `22.3.0`.
### Testing
@ -82,7 +82,7 @@ For each new detector, at least one regression tests must be present.
1. Create a folder in `tests/e2e/detectors/test_data` with the detector's argument name.
2. Create a test contract in `tests/e2e/detectors/test_data/<detector_name>/`.
3. Update `ALL_TESTS` in `tests/e2e/detectors/test_detectors.py`.
4. Run `python tests/e2e/detectors/test_detectors.py --compile` to create a zip file of the compilation artifacts.
4. Run `python tests/e2e/detectors/test_detectors.py --compile` to create a ZIP file of the compilation artifacts.
5. `pytest tests/e2e/detectors/test_detectors.py --insta update-new`. This will generate a snapshot of the detector output in `tests/e2e/detectors/snapshots/`. If updating an existing detector, run `pytest tests/e2e/detectors/test_detectors.py --insta review` and accept or reject the updates.
6. Run `pytest tests/e2e/detectors/test_detectors.py` to ensure everything worked. Then, add and commit the files to git.

@ -191,17 +191,18 @@ Num | Detector | What it Detects | Impact | Confidence
80 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
81 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
82 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
83 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High
84 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
85 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
86 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
87 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
88 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
89 | `cache-array-length` | [Detects `for` loops that use `length` member of some storage array in their loop condition and don't modify it.](https://github.com/crytic/slither/wiki/Detector-Documentation#cache-array-length) | Optimization | High
90 | `constable-states` | [State variables that could be declared constant](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant) | Optimization | High
91 | `external-function` | [Public function that could be declared external](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external) | Optimization | High
92 | `immutable-states` | [State variables that could be declared immutable](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-immutable) | Optimization | High
93 | `var-read-using-this` | [Contract reads its own variable using `this`](https://github.com/crytic/slither/wiki/Detector-Documentation#public-variable-read-in-external-context) | Optimization | High
83 | `unused-import` | [Detects unused imports](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-imports) | Informational | High
84 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High
85 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
86 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
87 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
88 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
89 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
90 | `cache-array-length` | [Detects `for` loops that use `length` member of some storage array in their loop condition and don't modify it.](https://github.com/crytic/slither/wiki/Detector-Documentation#cache-array-length) | Optimization | High
91 | `constable-states` | [State variables that could be declared constant](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant) | Optimization | High
92 | `external-function` | [Public function that could be declared external](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external) | Optimization | High
93 | `immutable-states` | [State variables that could be declared immutable](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-immutable) | Optimization | High
94 | `var-read-using-this` | [Contract reads its own variable using `this`](https://github.com/crytic/slither/wiki/Detector-Documentation#public-variable-read-in-external-context) | Optimization | High
For more information, see
@ -289,16 +290,16 @@ Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailt
Title | Usage | Authors | Venue | Code
--- | --- | --- | --- | ---
[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
[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 | [MPro](https://github.com/QuanZhang-William/M-Pro)
[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
[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 | [SCRepair](https://github.com/xiaoly8/SCRepair/)
[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)
[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) | -
[SAILFISH: Vetting Smart Contract State-Inconsistency Bugs in Seconds](https://arxiv.org/pdf/2104.08638.pdf) | Rely on SlithIR to build a *storage dependency graph* | Priyanka Bose, Dipanjan Das, Yanju Chen, Yu Feng, Christopher Kruegel, and Giovanni Vigna | S&P 22 | [Sailfish](https://github.com/ucsb-seclab/sailfish)
[SolType: Refinement Types for Arithmetic Overflow in Solidity](https://arxiv.org/abs/2110.00677) | Use Slither as frontend to build refinement type system | Bryan Tan, Benjamin Mariano, Shuvendu K. Lahiri, Isil Dillig, Yu Feng | POPL 22
[Do Not Rug on Me: Leveraging Machine Learning Techniques for Automated Scam Detection](https://www.mdpi.com/2227-7390/10/6/949) | Use Slither to extract tokens' features (mintable, pausable, ..) | Mazorra, Bruno, Victor Adan, and Vanesa Daza | Mathematics 10.6 (2022)
[SolType: Refinement Types for Arithmetic Overflow in Solidity](https://arxiv.org/abs/2110.00677) | Use Slither as frontend to build refinement type system | Bryan Tan, Benjamin Mariano, Shuvendu K. Lahiri, Isil Dillig, Yu Feng | POPL 22 | -
[Do Not Rug on Me: Leveraging Machine Learning Techniques for Automated Scam Detection](https://www.mdpi.com/2227-7390/10/6/949) | Use Slither to extract tokens' features (mintable, pausable, ..) | Mazorra, Bruno, Victor Adan, and Vanesa Daza | Mathematics 10.6 (2022) | -
[MANDO: Multi-Level Heterogeneous Graph Embeddings for Fine-Grained Detection of Smart Contract Vulnerabilities](https://arxiv.org/abs/2208.13252) | Use Slither to extract the CFG and call graph | Hoang Nguyen, Nhat-Minh Nguyen, Chunyao Xie, Zahra Ahmadi, Daniel Kudendo, Thanh-Nam Doan and Lingxiao Jiang| IEEE 9th International Conference on Data Science and Advanced Analytics (DSAA, 2022) | [ge-sc](https://github.com/MANDO-Project/ge-sc)
[Automated Auditing of Price Gouging TOD Vulnerabilities in Smart Contracts](https://www.cs.toronto.edu/~fanl/papers/price-icbc22.pdf) | Use Slither to extract the CFG and data dependencies| Sidi Mohamed Beillahi, Eric Keilty, Keerthi Nelaturu, Andreas Veneris, and Fan Long | 2022 IEEE International Conference on Blockchain and Cryptocurrency (ICBC) | [Smart-Contract-Repair](https://github.com/Veneris-Group/TOD-Location-Rectification)

@ -7,7 +7,6 @@ missing-module-docstring,
missing-class-docstring,
missing-function-docstring,
unnecessary-lambda,
bad-continuation,
cyclic-import,
line-too-long,
invalid-name,
@ -18,5 +17,6 @@ logging-fstring-interpolation,
logging-not-lazy,
duplicate-code,
import-error,
unsubscriptable-object
unsubscriptable-object,
unnecessary-lambda-assignment
"""

@ -0,0 +1,28 @@
import json
from pathlib import Path
import urllib.request
def retrieve_json(url):
with urllib.request.urlopen(url) as response:
data = response.read().decode("utf-8")
return json.loads(data)
def organize_data(json_data):
version_bugs = {}
for version, info in json_data.items():
version_bugs[version] = info["bugs"]
return version_bugs
if __name__ == "__main__":
bug_list_url = (
"https://raw.githubusercontent.com/ethereum/solidity/develop/docs/bugs_by_version.json"
)
bug_data = retrieve_json(bug_list_url)
bugs_by_version = organize_data(bug_data)
with open(Path.cwd() / Path("slither/utils/buggy_versions.py"), "w", encoding="utf-8") as file:
file.write("# pylint: disable=too-many-lines\n")
file.write(f"bugs_by_version = {bugs_by_version}")

@ -8,14 +8,14 @@ setup(
description="Slither is a Solidity and Vyper static analysis framework written in Python 3.",
url="https://github.com/crytic/slither",
author="Trail of Bits",
version="0.10.1",
version="0.10.2",
packages=find_packages(),
python_requires=">=3.8",
install_requires=[
"packaging",
"prettytable>=3.3.0",
"pycryptodome>=3.4.6",
"crytic-compile>=0.3.6,<0.4.0",
"crytic-compile>=0.3.7,<0.4.0",
# "crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile",
"web3>=6.0.0",
"eth-abi>=4.0.0",
@ -25,7 +25,7 @@ setup(
extras_require={
"lint": [
"black==22.3.0",
"pylint==2.13.4",
"pylint==3.0.3",
],
"test": [
"pytest",

@ -10,9 +10,9 @@ import os
import pstats
import sys
import traceback
from importlib import metadata
from typing import Tuple, Optional, List, Dict, Type, Union, Any, Sequence
from pkg_resources import iter_entry_points, require
from crytic_compile import cryticparser, CryticCompile
from crytic_compile.platform.standard import generate_standard_export
@ -166,19 +166,26 @@ def get_detectors_and_printers() -> Tuple[
printers = [p for p in printers_ if inspect.isclass(p) and issubclass(p, AbstractPrinter)]
# Handle plugins!
for entry_point in iter_entry_points(group="slither_analyzer.plugin", name=None):
if sys.version_info >= (3, 10):
entry_points = metadata.entry_points(group="slither_analyzer.plugin")
else:
from pkg_resources import iter_entry_points # pylint: disable=import-outside-toplevel
entry_points = iter_entry_points(group="slither_analyzer.plugin", name=None)
for entry_point in entry_points:
make_plugin = entry_point.load()
plugin_detectors, plugin_printers = make_plugin()
detector = None
if not all(issubclass(detector, AbstractDetector) for detector in plugin_detectors):
raise Exception(
raise ValueError(
f"Error when loading plugin {entry_point}, {detector} is not a detector"
)
printer = None
if not all(issubclass(printer, AbstractPrinter) for printer in plugin_printers):
raise Exception(f"Error when loading plugin {entry_point}, {printer} is not a printer")
raise ValueError(f"Error when loading plugin {entry_point}, {printer} is not a printer")
# We convert those to lists in case someone returns a tuple
detectors += list(plugin_detectors)
@ -208,7 +215,7 @@ def choose_detectors(
if detector in detectors:
detectors_to_run.append(detectors[detector])
else:
raise Exception(f"Error: {detector} is not a detector")
raise ValueError(f"Error: {detector} is not a detector")
detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT)
return detectors_to_run
@ -256,7 +263,7 @@ def choose_printers(
if printer in printers:
printers_to_run.append(printers[printer])
else:
raise Exception(f"Error: {printer} is not a printer")
raise ValueError(f"Error: {printer} is not a printer")
return printers_to_run
@ -298,7 +305,7 @@ def parse_args(
parser.add_argument(
"--version",
help="displays the current version",
version=require("slither-analyzer")[0].version,
version=metadata.version("slither-analyzer"),
action="version",
)
@ -648,7 +655,7 @@ def parse_args(
args.json_types = set(args.json_types.split(",")) # type:ignore
for json_type in args.json_types:
if json_type not in JSON_OUTPUT_TYPES:
raise Exception(f'Error: "{json_type}" is not a valid JSON result output type.')
raise ValueError(f'Error: "{json_type}" is not a valid JSON result output type.')
return args

@ -9,9 +9,8 @@ from typing import Optional, List, Dict, Callable, Tuple, TYPE_CHECKING, Union,
from crytic_compile.platform import Type as PlatformType
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.utils.using_for import USING_FOR, merge_using_for
from slither.core.declarations.function import Function, FunctionType, FunctionLanguage
from slither.utils.erc import (
ERC20_signatures,
@ -50,9 +49,6 @@ if TYPE_CHECKING:
LOGGER = logging.getLogger("Contract")
USING_FOR_KEY = Union[str, Type]
USING_FOR_ITEM = List[Union[Type, Function]]
class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
@ -87,12 +83,13 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._type_aliases: Dict[str, "TypeAliasContract"] = {}
# The only str is "*"
self._using_for: Dict[USING_FOR_KEY, USING_FOR_ITEM] = {}
self._using_for_complete: Optional[Dict[USING_FOR_KEY, USING_FOR_ITEM]] = None
self._using_for: USING_FOR = {}
self._using_for_complete: Optional[USING_FOR] = None
self._kind: Optional[str] = None
self._is_interface: bool = False
self._is_library: bool = False
self._is_fully_implemented: bool = False
self._is_abstract: bool = False
self._signatures: Optional[List[str]] = None
self._signatures_declared: Optional[List[str]] = None
@ -203,12 +200,34 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
@property
def is_fully_implemented(self) -> bool:
"""
bool: True if the contract defines all functions.
In modern Solidity, virtual functions can lack an implementation.
Prior to Solidity 0.6.0, functions like the following would be not fully implemented:
```solidity
contract ImplicitAbstract{
function f() public;
}
```
"""
return self._is_fully_implemented
@is_fully_implemented.setter
def is_fully_implemented(self, is_fully_implemented: bool):
self._is_fully_implemented = is_fully_implemented
@property
def is_abstract(self) -> bool:
"""
Note for Solidity < 0.6.0 it will always be false
bool: True if the contract is abstract.
"""
return self._is_abstract
@is_abstract.setter
def is_abstract(self, is_abstract: bool):
self._is_abstract = is_abstract
# endregion
###################################################################################
###################################################################################
@ -310,29 +329,20 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
@property
def using_for(self) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
def using_for(self) -> USING_FOR:
return self._using_for
@property
def using_for_complete(self) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
def using_for_complete(self) -> USING_FOR:
"""
Dict[Union[str, Type], List[Type]]: Dict of merged local using for directive with top level directive
USING_FOR: Dict of merged local using for directive with top level directive
"""
def _merge_using_for(
uf1: Dict[USING_FOR_KEY, USING_FOR_ITEM], uf2: Dict[USING_FOR_KEY, USING_FOR_ITEM]
) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
result = {**uf1, **uf2}
for key, value in result.items():
if key in uf1 and key in uf2:
result[key] = value + uf1[key]
return result
if self._using_for_complete is None:
result = self.using_for
top_level_using_for = self.file_scope.using_for_directives
for uftl in top_level_using_for:
result = _merge_using_for(result, uftl.using_for)
result = merge_using_for(result, uftl.using_for)
self._using_for_complete = result
return self._using_for_complete
@ -996,16 +1006,14 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def get_functions_overridden_by(self, function: "Function") -> List["Function"]:
"""
Return the list of functions overriden by the function
Return the list of functions overridden by the function
Args:
(core.Function)
Returns:
list(core.Function)
"""
candidatess = [c.functions_declared for c in self.inheritance]
candidates = [candidate for sublist in candidatess for candidate in sublist]
return [f for f in candidates if f.full_name == function.full_name]
return function.overrides
# endregion
###################################################################################

@ -37,7 +37,7 @@ if TYPE_CHECKING:
HighLevelCallType,
LibraryCallType,
)
from slither.core.declarations import Contract
from slither.core.declarations import Contract, FunctionContract
from slither.core.cfg.node import Node, NodeType
from slither.core.variables.variable import Variable
from slither.slithir.variables.variable import SlithIRVariable
@ -46,7 +46,6 @@ if TYPE_CHECKING:
from slither.slithir.operations import Operation
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.scope.scope import FileScope
from slither.slithir.variables.state_variable import StateIRVariable
LOGGER = logging.getLogger("Function")
ReacheableNode = namedtuple("ReacheableNode", ["node", "ir"])
@ -126,6 +125,9 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self._pure: bool = False
self._payable: bool = False
self._visibility: Optional[str] = None
self._virtual: bool = False
self._overrides: List["FunctionContract"] = []
self._overridden_by: List["FunctionContract"] = []
self._is_implemented: Optional[bool] = None
self._is_empty: Optional[bool] = None
@ -175,6 +177,8 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self._all_library_calls: Optional[List["LibraryCallType"]] = None
self._all_low_level_calls: Optional[List["LowLevelCallType"]] = None
self._all_solidity_calls: Optional[List["SolidityFunction"]] = None
self._all_variables_read: Optional[List["Variable"]] = None
self._all_variables_written: Optional[List["Variable"]] = None
self._all_state_variables_read: Optional[List["StateVariable"]] = None
self._all_solidity_variables_read: Optional[List["SolidityVariable"]] = None
self._all_state_variables_written: Optional[List["StateVariable"]] = None
@ -351,8 +355,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
@property
def id(self) -> Optional[str]:
"""
Return the ID of the funciton. For Solidity with compact-AST the ID is the reference ID
For other, the ID is None
Return the reference ID of the function, if available.
:return:
:rtype:
@ -441,6 +444,49 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
def payable(self, p: bool):
self._payable = p
# endregion
###################################################################################
###################################################################################
# region Virtual
###################################################################################
###################################################################################
@property
def is_virtual(self) -> bool:
"""
Note for Solidity < 0.6.0 it will always be false
bool: True if the function is virtual
"""
return self._virtual
@is_virtual.setter
def is_virtual(self, v: bool):
self._virtual = v
@property
def is_override(self) -> bool:
"""
Note for Solidity < 0.6.0 it will always be false
bool: True if the function overrides a base function
"""
return len(self._overrides) > 0
@property
def overridden_by(self) -> List["FunctionContract"]:
"""
List["FunctionContract"]: List of functions in child contracts that override this function
This may include distinct instances of the same function due to inheritance
"""
return self._overridden_by
@property
def overrides(self) -> List["FunctionContract"]:
"""
List["FunctionContract"]: List of functions in parent contracts that this function overrides
This may include distinct instances of the same function due to inheritance
"""
return self._overrides
# endregion
###################################################################################
###################################################################################
@ -1108,6 +1154,18 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
return list(set(values))
def all_variables_read(self) -> List["Variable"]:
"""recursive version of variables_read"""
if self._all_variables_read is None:
self._all_variables_read = self._explore_functions(lambda x: x.variables_read)
return self._all_variables_read
def all_variables_written(self) -> List["Variable"]:
"""recursive version of variables_written"""
if self._all_variables_written is None:
self._all_variables_written = self._explore_functions(lambda x: x.variables_written)
return self._all_variables_written
def all_state_variables_read(self) -> List["StateVariable"]:
"""recursive version of variables_read"""
if self._all_state_variables_read is None:

@ -65,7 +65,10 @@ class FunctionContract(Function, ContractLevel):
@property
def file_scope(self) -> "FileScope":
return self.contract.file_scope
# This is the contract declarer's file scope because inherited functions have access
# to the file scope which their declared in. This scope may contain references not
# available in the child contract's scope. See inherited_function_scope.sol for an example.
return self.contract_declarer.file_scope
# endregion
###################################################################################

@ -1,10 +1,11 @@
"""
Function module
"""
from typing import Dict, List, Tuple, TYPE_CHECKING
from typing import Dict, List, Tuple, TYPE_CHECKING, Optional
from slither.core.declarations import Function
from slither.core.declarations.top_level import TopLevel
from slither.utils.using_for import USING_FOR, merge_using_for
if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
@ -16,11 +17,25 @@ class FunctionTopLevel(Function, TopLevel):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope") -> None:
super().__init__(compilation_unit)
self._scope: "FileScope" = scope
self._using_for_complete: Optional[USING_FOR] = None
@property
def file_scope(self) -> "FileScope":
return self._scope
@property
def using_for_complete(self) -> USING_FOR:
"""
USING_FOR: Dict of top level directive
"""
if self._using_for_complete is None:
result = {}
for uftl in self.file_scope.using_for_directives:
result = merge_using_for(result, uftl.using_for)
self._using_for_complete = result
return self._using_for_complete
@property
def canonical_name(self) -> str:
"""

@ -11,8 +11,8 @@ class Import(SourceMapping):
def __init__(self, filename: Path, scope: "FileScope") -> None:
super().__init__()
self._filename: Path = filename
self._alias: Optional[str] = None
self.scope: "FileScope" = scope
self._alias: Optional[str] = None
# Map local name -> original name
self.renaming: Dict[str, str] = {}

@ -116,6 +116,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"_abi_encode()": [],
"slice()": [],
"uint2str()": ["string"],
"send()": [],
}
@ -139,7 +140,7 @@ class SolidityVariable(SourceMapping):
self._name = name
# dev function, will be removed once the code is stable
def _check_name(self, name: str) -> None: # pylint: disable=no-self-use
def _check_name(self, name: str) -> None:
assert name in SOLIDITY_VARIABLES or name.endswith(("_slot", "_offset"))
@property

@ -1,8 +1,7 @@
from typing import TYPE_CHECKING, List, Dict, Union
from typing import TYPE_CHECKING
from slither.core.declarations.contract import USING_FOR_KEY, USING_FOR_ITEM
from slither.core.solidity_types.type import Type
from slither.core.declarations.top_level import TopLevel
from slither.utils.using_for import USING_FOR
if TYPE_CHECKING:
from slither.core.scope.scope import FileScope
@ -11,9 +10,9 @@ if TYPE_CHECKING:
class UsingForTopLevel(TopLevel):
def __init__(self, scope: "FileScope") -> None:
super().__init__()
self._using_for: Dict[Union[str, Type], List[Type]] = {}
self._using_for: USING_FOR = {}
self.file_scope: "FileScope" = scope
@property
def using_for(self) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
def using_for(self) -> USING_FOR:
return self._using_for

@ -29,6 +29,7 @@ class FileScope:
def __init__(self, filename: Filename) -> None:
self.filename = filename
self.accessible_scopes: List[FileScope] = []
self.exported_symbols: Set[int] = set()
self.contracts: Dict[str, Contract] = {}
# Custom error are a list instead of a dict
@ -36,11 +37,11 @@ class FileScope:
# So we simplify the logic and have the scope fields all populated
self.custom_errors: Set[CustomErrorTopLevel] = set()
self.enums: Dict[str, EnumTopLevel] = {}
self.events: Dict[str, EventTopLevel] = {}
# Functions is a list instead of a dict
# Because we parse the function signature later on
# So we simplify the logic and have the scope fields all populated
self.functions: Set[FunctionTopLevel] = set()
self.events: Set[EventTopLevel] = set()
self.using_for_directives: Set[UsingForTopLevel] = set()
self.imports: Set[Import] = set()
self.pragmas: Set[Pragma] = set()
@ -56,53 +57,39 @@ class FileScope:
# Name -> type alias
self.type_aliases: Dict[str, TypeAlias] = {}
def add_accesible_scopes(self) -> bool: # pylint: disable=too-many-branches
def add_accessible_scopes(self) -> bool: # pylint: disable=too-many-branches
"""
Add information from accessible scopes. Return true if new information was obtained
:return:
:rtype:
"""
learn_something = False
for new_scope in self.accessible_scopes:
if not _dict_contain(new_scope.contracts, self.contracts):
self.contracts.update(new_scope.contracts)
learn_something = True
if not new_scope.custom_errors.issubset(self.custom_errors):
self.custom_errors |= new_scope.custom_errors
learn_something = True
if not _dict_contain(new_scope.enums, self.enums):
self.enums.update(new_scope.enums)
learn_something = True
if not _dict_contain(new_scope.events, self.events):
self.events.update(new_scope.events)
learn_something = True
if not new_scope.functions.issubset(self.functions):
self.functions |= new_scope.functions
learn_something = True
# To support using for directives on user defined types and user defined functions,
# we need to propagate the using for directives from the imported file to the importing file
# since it is not reflected in the "exportedSymbols" field of the AST.
if not new_scope.using_for_directives.issubset(self.using_for_directives):
self.using_for_directives |= new_scope.using_for_directives
learn_something = True
if not new_scope.imports.issubset(self.imports):
self.imports |= new_scope.imports
learn_something = True
if not new_scope.pragmas.issubset(self.pragmas):
self.pragmas |= new_scope.pragmas
if not _dict_contain(new_scope.type_aliases, self.type_aliases):
self.type_aliases.update(new_scope.type_aliases)
learn_something = True
if not _dict_contain(new_scope.structures, self.structures):
self.structures.update(new_scope.structures)
if not new_scope.functions.issubset(self.functions):
self.functions |= new_scope.functions
learn_something = True
if not _dict_contain(new_scope.variables, self.variables):
self.variables.update(new_scope.variables)
# To get around this bug for aliases https://github.com/ethereum/solidity/pull/11881,
# we propagate the exported_symbols from the imported file to the importing file
# See tests/e2e/solc_parsing/test_data/top-level-nested-import-0.7.1.sol
if not new_scope.exported_symbols.issubset(self.exported_symbols):
self.exported_symbols |= new_scope.exported_symbols
learn_something = True
# This is need to support aliasing when we do a late lookup using SolidityImportPlaceholder
if not _dict_contain(new_scope.renaming, self.renaming):
self.renaming.update(new_scope.renaming)
learn_something = True
if not _dict_contain(new_scope.type_aliases, self.type_aliases):
self.type_aliases.update(new_scope.type_aliases)
learn_something = True
return learn_something

@ -22,7 +22,7 @@ from slither.core.source_mapping.source_mapping import SourceMapping, Source
from slither.slithir.variables import Constant
from slither.utils.colors import red
from slither.utils.sarif import read_triage_info
from slither.utils.source_mapping import get_definition, get_references, get_implementation
from slither.utils.source_mapping import get_definition, get_references, get_all_implementations
logger = logging.getLogger("Slither")
logging.basicConfig()
@ -204,41 +204,53 @@ class SlitherCore(Context):
def _compute_offsets_from_thing(self, thing: SourceMapping):
definition = get_definition(thing, self.crytic_compile)
references = get_references(thing)
implementation = get_implementation(thing)
implementations = get_all_implementations(thing, self.contracts)
for offset in range(definition.start, definition.end + 1):
if (
isinstance(thing, TopLevel)
isinstance(thing, (TopLevel, Contract))
or (
isinstance(thing, FunctionContract)
and thing.contract_declarer == thing.contract
)
or (isinstance(thing, ContractLevel) and not isinstance(thing, FunctionContract))
):
self._offset_to_objects[definition.filename][offset].add(thing)
self._offset_to_definitions[definition.filename][offset].add(definition)
self._offset_to_implementations[definition.filename][offset].add(implementation)
self._offset_to_implementations[definition.filename][offset].update(implementations)
self._offset_to_references[definition.filename][offset] |= set(references)
for ref in references:
for offset in range(ref.start, ref.end + 1):
is_declared_function = (
isinstance(thing, FunctionContract)
and thing.contract_declarer == thing.contract
)
if (
isinstance(thing, TopLevel)
or (
isinstance(thing, FunctionContract)
and thing.contract_declarer == thing.contract
)
or is_declared_function
or (
isinstance(thing, ContractLevel) and not isinstance(thing, FunctionContract)
)
):
self._offset_to_objects[definition.filename][offset].add(thing)
self._offset_to_definitions[ref.filename][offset].add(definition)
self._offset_to_implementations[ref.filename][offset].add(implementation)
if is_declared_function:
# Only show the nearest lexical definition for declared contract-level functions
if (
thing.contract.source_mapping.start
< offset
< thing.contract.source_mapping.end
):
self._offset_to_definitions[ref.filename][offset].add(definition)
else:
self._offset_to_definitions[ref.filename][offset].add(definition)
self._offset_to_implementations[ref.filename][offset].update(implementations)
self._offset_to_references[ref.filename][offset] |= set(references)
def _compute_offsets_to_ref_impl_decl(self): # pylint: disable=too-many-branches
@ -251,15 +263,18 @@ class SlitherCore(Context):
for contract in compilation_unit.contracts:
self._compute_offsets_from_thing(contract)
for function in contract.functions:
for function in contract.functions_declared:
self._compute_offsets_from_thing(function)
for variable in function.local_variables:
self._compute_offsets_from_thing(variable)
for modifier in contract.modifiers:
for modifier in contract.modifiers_declared:
self._compute_offsets_from_thing(modifier)
for variable in modifier.local_variables:
self._compute_offsets_from_thing(variable)
for var in contract.state_variables:
self._compute_offsets_from_thing(var)
for st in contract.structures:
self._compute_offsets_from_thing(st)
@ -268,6 +283,10 @@ class SlitherCore(Context):
for event in contract.events:
self._compute_offsets_from_thing(event)
for typ in contract.type_aliases:
self._compute_offsets_from_thing(typ)
for enum in compilation_unit.enums_top_level:
self._compute_offsets_from_thing(enum)
for event in compilation_unit.events_top_level:
@ -276,6 +295,14 @@ class SlitherCore(Context):
self._compute_offsets_from_thing(function)
for st in compilation_unit.structures_top_level:
self._compute_offsets_from_thing(st)
for var in compilation_unit.variables_top_level:
self._compute_offsets_from_thing(var)
for typ in compilation_unit.type_aliases.values():
self._compute_offsets_from_thing(typ)
for err in compilation_unit.custom_errors:
self._compute_offsets_from_thing(err)
for event in compilation_unit.events_top_level:
self._compute_offsets_from_thing(event)
for import_directive in compilation_unit.import_directives:
self._compute_offsets_from_thing(import_directive)
for pragma in compilation_unit.pragma_directives:

@ -74,7 +74,7 @@ class ArrayType(Type):
def __eq__(self, other: Any) -> bool:
if not isinstance(other, ArrayType):
return False
return self._type == other.type and self.length == other.length
return self._type == other.type and self._length_value == other.length_value
def __hash__(self) -> int:
return hash(str(self))

@ -98,3 +98,4 @@ from .operations.incorrect_exp import IncorrectOperatorExponentiation
from .statements.tautological_compare import TautologicalCompare
from .statements.return_bomb import ReturnBomb
from .functions.out_of_order_retryable import OutOfOrderRetryable
from .statements.unused_import import UnusedImport

@ -1,6 +1,7 @@
"""
Check that the same pragma is used in all the files
"""
from collections import OrderedDict
from typing import List, Dict
from slither.core.compilation_unit import SlitherCompilationUnit
@ -31,16 +32,25 @@ class ConstantPragma(AbstractDetector):
def _detect(self) -> List[Output]:
results = []
pragma = self.compilation_unit.pragma_directives
versions = [p.version for p in pragma if p.is_solidity_version]
versions = sorted(list(set(versions)))
pragma_directives_by_version = OrderedDict()
for pragma in self.compilation_unit.pragma_directives:
if pragma.is_solidity_version:
if pragma.version not in pragma_directives_by_version:
pragma_directives_by_version[
pragma.version
] = f"\t\t- {str(pragma.source_mapping)}\n"
else:
pragma_directives_by_version[
pragma.version
] += f"\t\t- {str(pragma.source_mapping)}\n"
versions = list(pragma_directives_by_version.keys())
if len(versions) > 1:
info: DETECTOR_INFO = ["Different versions of Solidity are used:\n"]
info += [f"\t- Version used: {[str(v) for v in versions]}\n"]
info: DETECTOR_INFO = [f"{len(versions)} different versions of Solidity are used:\n"]
for p in sorted(pragma, key=lambda x: x.version):
info += ["\t- ", p, "\n"]
for version in versions:
pragma = pragma_directives_by_version[version]
info += [f"\t- Version constraint {version} is used by:\n {pragma}"]
res = self.generate_result(info)

@ -12,6 +12,7 @@ from slither.detectors.abstract_detector import (
)
from slither.formatters.attributes.incorrect_solc import custom_format
from slither.utils.output import Output
from slither.utils.buggy_versions import bugs_by_version
# group:
# 0: ^ > >= < <= (optional)
@ -46,64 +47,36 @@ We also recommend avoiding complex `pragma` statement."""
# region wiki_recommendation
WIKI_RECOMMENDATION = """
Deploy with any of the following Solidity versions:
- 0.8.18
The recommendations take into account:
- Risks related to recent releases
- Risks of complex code generation changes
- Risks of new language features
- Risks of known bugs
Deploy with a recent version of Solidity (at least 0.8.0) with no known severe issues.
Use a simple pragma version that allows any of these versions.
Consider using the latest version of Solidity for testing."""
# endregion wiki_recommendation
COMPLEX_PRAGMA_TXT = "is too complex"
OLD_VERSION_TXT = "allows old versions"
OLD_VERSION_TXT = (
"is an outdated solc version. Use a more recent version (at least 0.8.0), if possible."
)
LESS_THAN_TXT = "uses lesser than"
BUGGY_VERSION_TXT = (
"is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)"
"contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)"
)
# Indicates the allowed versions. Must be formatted in increasing order.
ALLOWED_VERSIONS = ["0.8.18"]
TOO_RECENT_VERSION_TXT = f"necessitates a version too recent to be trusted. Consider deploying with {'/'.join(ALLOWED_VERSIONS)}."
# Indicates the versions that should not be used.
BUGGY_VERSIONS = [
"0.4.22",
"^0.4.22",
"0.5.5",
"^0.5.5",
"0.5.6",
"^0.5.6",
"0.5.14",
"^0.5.14",
"0.6.9",
"^0.6.9",
"0.8.8",
"^0.8.8",
]
ALLOWED_VERSIONS = ["0.8.0"]
def _check_version(self, version: Tuple[str, str, str, str, str]) -> Optional[str]:
op = version[0]
if op and op not in [">", ">=", "^"]:
return self.LESS_THAN_TXT
version_number = ".".join(version[2:])
if version_number in self.BUGGY_VERSIONS:
return self.BUGGY_VERSION_TXT
if version_number not in self.ALLOWED_VERSIONS:
if list(map(int, version[2:])) > list(map(int, self.ALLOWED_VERSIONS[-1].split("."))):
return self.TOO_RECENT_VERSION_TXT
return self.OLD_VERSION_TXT
if version_number in bugs_by_version:
bugs = "\n".join([f"\t- {bug}" for bug in bugs_by_version[version_number]])
return self.BUGGY_VERSION_TXT + f"\n{bugs}"
return None
def _check_pragma(self, version: str) -> Optional[str]:
if version in self.BUGGY_VERSIONS:
return self.BUGGY_VERSION_TXT
versions = PATTERN.findall(version)
if len(versions) == 1:
version = versions[0]
@ -130,42 +103,43 @@ Consider using the latest version of Solidity for testing."""
# Detect all version related pragmas and check if they are disallowed.
results = []
pragma = self.compilation_unit.pragma_directives
disallowed_pragmas = []
disallowed_pragmas = {}
for p in pragma:
# Skip any pragma directives which do not refer to version
if len(p.directive) < 1 or p.directive[0] != "solidity":
continue
# This is version, so we test if this is disallowed.
reason = self._check_pragma(p.version)
if reason:
disallowed_pragmas.append((reason, p))
if reason is None:
continue
if p.version in disallowed_pragmas and reason in disallowed_pragmas[p.version]:
disallowed_pragmas[p.version][reason] += f"\t- {str(p.source_mapping)}\n"
else:
disallowed_pragmas[p.version] = {reason: f"\t- {str(p.source_mapping)}\n"}
# If we found any disallowed pragmas, we output our findings.
if disallowed_pragmas:
for (reason, p) in disallowed_pragmas:
info: DETECTOR_INFO = ["Pragma version", p, f" {reason}\n"]
if len(disallowed_pragmas.keys()):
for p, reasons in disallowed_pragmas.items():
info: DETECTOR_INFO = []
for r, v in reasons.items():
info += [f"Version constraint {p} {r}.\n It is used by:\n{v}"]
json = self.generate_result(info)
results.append(json)
if self.compilation_unit.solc_version not in self.ALLOWED_VERSIONS:
if self.compilation_unit.solc_version in self.BUGGY_VERSIONS:
info = [
"solc-",
self.compilation_unit.solc_version,
" ",
self.BUGGY_VERSION_TXT,
]
else:
info = [
"solc-",
self.compilation_unit.solc_version,
" is not recommended for deployment\n",
]
if list(map(int, self.compilation_unit.solc_version.split("."))) < list(
map(int, self.ALLOWED_VERSIONS[-1].split("."))
):
info = [
"solc-",
self.compilation_unit.solc_version,
" ",
self.OLD_VERSION_TXT,
"\n",
]
json = self.generate_result(info)

@ -89,7 +89,7 @@ If the condition in `myModif` is false, the execution of `get()` will return 0."
info: DETECTOR_INFO = [
"Modifier ",
mod,
" does not always execute _; or revert",
" does not always execute _; or revert\n",
]
res = self.generate_result(info)

@ -39,5 +39,5 @@ If `send` is used to prevent blocking operations, consider logging the failed `s
WIKI_RECOMMENDATION = "Ensure that the return value of `send` is checked or logged."
def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use
def _is_instance(self, ir: Operation) -> bool:
return isinstance(ir, Send)

@ -46,7 +46,7 @@ Several tokens do not revert in case of failure and return false. If one of thes
"Use `SafeERC20`, or ensure that the transfer/transferFrom return value is checked."
)
def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use
def _is_instance(self, ir: Operation) -> bool:
return (
isinstance(ir, HighLevelCall)
and isinstance(ir.function, Function)

@ -49,7 +49,7 @@ contract MyConc{
WIKI_RECOMMENDATION = "Ensure that all the return values of the function calls are used."
def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use
def _is_instance(self, ir: Operation) -> bool:
return (
isinstance(ir, HighLevelCall)
and (
@ -64,9 +64,7 @@ contract MyConc{
and isinstance(ir, (Assignment, Unpack))
)
def detect_unused_return_values(
self, f: FunctionContract
) -> List[Node]: # pylint: disable=no-self-use
def detect_unused_return_values(self, f: FunctionContract) -> List[Node]:
"""
Return the nodes where the return value of a call is unused
Args:

@ -1,6 +1,8 @@
from collections import defaultdict
from typing import List
from crytic_compile.platform import Type as PlatformType
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.declarations import Contract
from slither.detectors.abstract_detector import (
@ -61,6 +63,8 @@ As a result, the second contract cannot be analyzed.
def _detect(self) -> List[Output]:
results = []
compilation_unit = self.compilation_unit
if compilation_unit.core.crytic_compile.platform != PlatformType.TRUFFLE:
return []
all_contracts = compilation_unit.contracts
all_contracts_name = [c.name for c in all_contracts]

@ -40,7 +40,7 @@ class TooManyDigits(AbstractDetector):
# region wiki_description
WIKI_DESCRIPTION = """
Literals with many digits are difficult to read and review.
Literals with many digits are difficult to read and review. Use scientific notation or suffixes to make the code more readable.
"""
# endregion wiki_description

@ -0,0 +1,75 @@
from typing import List
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification, Output
# pylint: disable=protected-access,too-many-nested-blocks
class UnusedImport(AbstractDetector):
"""
Detector unused imports.
"""
ARGUMENT = "unused-import"
HELP = "Detects unused imports"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#unused-imports"
WIKI_TITLE = "Unused Imports"
WIKI_DESCRIPTION = "Importing a file that is not used in the contract likely indicates a mistake. The import should be removed until it is needed."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
import {A} from "./A.sol";
contract B {}
```
B either should import from A and it was forgotten or the import is not needed and should be removed.
"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = (
"Remove the unused import. If the import is needed later, it can be added back."
)
def _detect(self) -> List[Output]:
results: List[Output] = []
# This is computed lazily and then memoized so we need to trigger the computation.
self.slither._compute_offsets_to_ref_impl_decl()
for unit in self.slither.compilation_units:
for filename, scope in unit.scopes.items():
unused = []
for i in scope.imports:
# `scope.imports` contains all transitive imports so we need to filter out imports not explicitly imported in the file.
# Otherwise, we would recommend removing an import that is used by a leaf contract and cause compilation errors.
if i.scope != scope:
continue
import_path = self.slither.crytic_compile.filename_lookup(i.filename)
use_found = False
# Search through all references to the imported file
for _, refs in self.slither._offset_to_references[import_path].items():
for ref in refs:
# If there is a reference in this file to the imported file, it is used.
if ref.filename == filename:
use_found = True
break
if use_found:
break
if not use_found:
unused.append(f"{i.source_mapping.content} ({i.source_mapping})")
if len(unused) > 0:
unused_list = "\n\t-" + "\n\t-".join(unused)
results.append(
self.generate_result(
[
f"The following unused import(s) in {filename.used} should be removed: {unused_list}\n",
]
)
)
return results

@ -501,7 +501,7 @@ def _explore_variables_declaration( # pylint: disable=too-many-arguments,too-ma
idx_beginning = func.source_mapping.start
idx_beginning += -func.source_mapping.starting_column + 1
idx_beginning += -sum([len(c) for c in potential_comments])
idx_beginning += -sum(len(c) for c in potential_comments)
old_comment = f"@param {old_str}".encode("utf8")

@ -20,7 +20,7 @@ class AbstractPrinter(metaclass=abc.ABCMeta):
WIKI = ""
def __init__(self, slither: "Slither", logger: Logger) -> None:
def __init__(self, slither: "Slither", logger: Optional[Logger]) -> None:
self.slither = slither
self.contracts = slither.contracts
self.filename = slither.filename

@ -100,10 +100,11 @@ class PrinterInheritanceGraph(AbstractPrinter):
# Add arrows (number them if there is more than one path so we know order of declaration for inheritance).
if len(contract.immediate_inheritance) == 1:
ret += f"{contract.name} -> {contract.immediate_inheritance[0]};\n"
immediate_inheritance = contract.immediate_inheritance[0]
ret += f"c{contract.id}_{contract.name} -> c{immediate_inheritance.id}_{immediate_inheritance};\n"
else:
for i, immediate_inheritance in enumerate(contract.immediate_inheritance):
ret += f'{contract.name} -> {immediate_inheritance} [ label="{i + 1}" ];\n'
ret += f'c{contract.id}_{contract.name} -> c{immediate_inheritance.id}_{immediate_inheritance} [ label="{i + 1}" ];\n'
# Functions
visibilities = ["public", "external"]
@ -151,7 +152,7 @@ class PrinterInheritanceGraph(AbstractPrinter):
indirect_shadowing_information = self._get_indirect_shadowing_information(contract)
# Build the node label
ret += f'{contract.name}[shape="box"'
ret += f'c{contract.id}_{contract.name}[shape="box"'
ret += 'label=< <TABLE border="0">'
ret += f'<TR><TD align="center"><B>{contract.name}</B></TD></TR>'
if public_functions:

@ -21,18 +21,20 @@ class Declaration(AbstractPrinter):
txt += "\n# Contracts\n"
for contract in compilation_unit.contracts:
txt += f"# {contract.name}\n"
txt += f"\t- Declaration: {get_definition(contract, compilation_unit.core.crytic_compile).to_detailed_str()}\n"
txt += f"\t- Implementation: {get_implementation(contract).to_detailed_str()}\n"
contract_def = get_definition(contract, compilation_unit.core.crytic_compile)
txt += f"\t- Declaration: {contract_def.to_detailed_str()}\n"
txt += f"\t- Implementation(s): {[x.to_detailed_str() for x in list(self.slither.offset_to_implementations(contract.source_mapping.filename.absolute, contract_def.start))]}\n"
txt += (
f"\t- References: {[x.to_detailed_str() for x in get_references(contract)]}\n"
)
txt += "\n\t## Function\n"
for func in contract.functions:
for func in contract.functions_declared:
txt += f"\t\t- {func.canonical_name}\n"
txt += f"\t\t\t- Declaration: {get_definition(func, compilation_unit.core.crytic_compile).to_detailed_str()}\n"
txt += f"\t\t\t- Implementation: {get_implementation(func).to_detailed_str()}\n"
function_def = get_definition(func, compilation_unit.core.crytic_compile)
txt += f"\t\t\t- Declaration: {function_def.to_detailed_str()}\n"
txt += f"\t\t\t- Implementation(s): {[x.to_detailed_str() for x in list(self.slither.offset_to_implementations(func.source_mapping.filename.absolute, function_def.start))]}\n"
txt += f"\t\t\t- References: {[x.to_detailed_str() for x in get_references(func)]}\n"
txt += "\n\t## State variables\n"

@ -1,11 +1,10 @@
import logging
from typing import Union, List, ValuesView, Type, Dict, Optional
from typing import Union, List, Type, Dict, Optional
from crytic_compile import CryticCompile, InvalidCompilation
# pylint: disable= no-name-in-module
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.scope.scope import FileScope
from slither.core.slither_core import SlitherCore
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.exceptions import SlitherError
@ -27,32 +26,71 @@ def _check_common_things(
) -> None:
if not issubclass(cls, base_cls) or cls is base_cls:
raise Exception(
raise SlitherError(
f"You can't register {cls!r} as a {thing_name}. You need to pass a class that inherits from {base_cls.__name__}"
)
if any(type(obj) == cls for obj in instances_list): # pylint: disable=unidiomatic-typecheck
raise Exception(f"You can't register {cls!r} twice.")
raise SlitherError(f"You can't register {cls!r} twice.")
def _update_file_scopes(candidates: ValuesView[FileScope]):
def _update_file_scopes(
sol_parser: SlitherCompilationUnitSolc,
): # pylint: disable=too-many-branches
"""
Because solc's import allows cycle in the import
We iterate until we aren't adding new information to the scope
Since all definitions in a file are exported by default, including definitions from its (transitive) dependencies,
we can identify all top level items that could possibly be referenced within the file from its exportedSymbols.
It is not as straightforward for user defined types and functions as well as aliasing. See add_accessible_scopes for more details.
"""
candidates = sol_parser.compilation_unit.scopes.values()
learned_something = False
# Because solc's import allows cycle in the import graph, iterate until we aren't adding new information to the scope.
while True:
for candidate in candidates:
learned_something |= candidate.add_accesible_scopes()
learned_something |= candidate.add_accessible_scopes()
if not learned_something:
break
learned_something = False
for scope in candidates:
for refId in scope.exported_symbols:
if refId in sol_parser.contracts_by_id:
contract = sol_parser.contracts_by_id[refId]
scope.contracts[contract.name] = contract
elif refId in sol_parser.functions_by_id:
functions = sol_parser.functions_by_id[refId]
assert len(functions) == 1
scope.functions.add(functions[0])
elif refId in sol_parser.imports_by_id:
import_directive = sol_parser.imports_by_id[refId]
scope.imports.add(import_directive)
elif refId in sol_parser.top_level_variables_by_id:
top_level_variable = sol_parser.top_level_variables_by_id[refId]
scope.variables[top_level_variable.name] = top_level_variable
elif refId in sol_parser.top_level_events_by_id:
top_level_event = sol_parser.top_level_events_by_id[refId]
scope.events.add(top_level_event)
elif refId in sol_parser.top_level_structures_by_id:
top_level_struct = sol_parser.top_level_structures_by_id[refId]
scope.structures[top_level_struct.name] = top_level_struct
elif refId in sol_parser.top_level_type_aliases_by_id:
top_level_type_alias = sol_parser.top_level_type_aliases_by_id[refId]
scope.type_aliases[top_level_type_alias.name] = top_level_type_alias
elif refId in sol_parser.top_level_enums_by_id:
top_level_enum = sol_parser.top_level_enums_by_id[refId]
scope.enums[top_level_enum.name] = top_level_enum
elif refId in sol_parser.top_level_errors_by_id:
top_level_custom_error = sol_parser.top_level_errors_by_id[refId]
scope.custom_errors.add(top_level_custom_error)
else:
logger.error(
f"Failed to resolved name for reference id {refId} in {scope.filename.absolute}."
)
class Slither(
SlitherCore
): # pylint: disable=too-many-instance-attributes,too-many-locals,too-many-statements
): # pylint: disable=too-many-instance-attributes,too-many-locals,too-many-statements,too-many-branches
def __init__(self, target: Union[str, CryticCompile], **kwargs) -> None:
"""
Args:
@ -118,7 +156,18 @@ class Slither(
sol_parser.parse_top_level_items(ast, path)
self.add_source_code(path)
_update_file_scopes(compilation_unit_slither.scopes.values())
for contract in sol_parser._underlying_contract_to_parser:
if contract.name.startswith("SlitherInternalTopLevelContract"):
raise SlitherError(
# region multi-line-string
"""Your codebase has a contract named 'SlitherInternalTopLevelContract'.
Please rename it, this name is reserved for Slither's internals"""
# endregion multi-line
)
sol_parser._contracts_by_id[contract.id] = contract
sol_parser._compilation_unit.contracts.append(contract)
_update_file_scopes(sol_parser)
if kwargs.get("generate_patches", False):
self.generate_patches = True

@ -1,6 +1,6 @@
import logging
from pathlib import Path
from typing import Any, List, TYPE_CHECKING, Union, Optional, Dict
from typing import Any, List, TYPE_CHECKING, Union, Optional
# pylint: disable= too-many-lines,import-outside-toplevel,too-many-branches,too-many-statements,too-many-nested-blocks
from slither.core.declarations import (
@ -13,7 +13,6 @@ from slither.core.declarations import (
SolidityVariableComposed,
Structure,
)
from slither.core.declarations.contract import USING_FOR_KEY, USING_FOR_ITEM
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.function_contract import FunctionContract
from slither.core.declarations.function_top_level import FunctionTopLevel
@ -84,6 +83,7 @@ from slither.slithir.variables import Constant, ReferenceVariable, TemporaryVari
from slither.slithir.variables import TupleVariable
from slither.utils.function import get_function_id
from slither.utils.type import export_nested_types_from_variable
from slither.utils.using_for import USING_FOR
from slither.visitors.slithir.expression_to_slithir import ExpressionToSlithIR
if TYPE_CHECKING:
@ -594,11 +594,13 @@ def _convert_type_contract(ir: Member) -> Assignment:
def propagate_types(ir: Operation, node: "Node"): # pylint: disable=too-many-locals
# propagate the type
node_function = node.function
using_for: Dict[USING_FOR_KEY, USING_FOR_ITEM] = (
node_function.contract.using_for_complete
if isinstance(node_function, FunctionContract)
else {}
)
using_for: USING_FOR = {}
if isinstance(node_function, FunctionContract):
using_for = node_function.contract.using_for_complete
elif isinstance(node_function, FunctionTopLevel):
using_for = node_function.using_for_complete
if isinstance(ir, OperationWithLValue) and ir.lvalue:
# Force assignment in case of missing previous correct type
if not ir.lvalue.type:
@ -871,9 +873,7 @@ def propagate_types(ir: Operation, node: "Node"): # pylint: disable=too-many-lo
elif isinstance(ir, NewArray):
ir.lvalue.set_type(ir.array_type)
elif isinstance(ir, NewContract):
contract = node.file_scope.get_contract_from_name(ir.contract_name)
assert contract
ir.lvalue.set_type(UserDefinedType(contract))
ir.lvalue.set_type(ir.contract_name)
elif isinstance(ir, NewElementaryType):
ir.lvalue.set_type(ir.type)
elif isinstance(ir, NewStructure):
@ -1164,7 +1164,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
return n
if isinstance(ins.ori, TmpNewContract):
op = NewContract(Constant(ins.ori.contract_name), ins.lvalue)
op = NewContract(ins.ori.contract_name, ins.lvalue)
op.set_expression(ins.expression)
op.call_id = ins.call_id
if ins.call_value:
@ -1209,7 +1209,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
internalcall.set_expression(ins.expression)
return internalcall
raise Exception(f"Not extracted {type(ins.called)} {ins}")
raise SlithIRError(f"Not extracted {type(ins.called)} {ins}")
# endregion
@ -1502,7 +1502,6 @@ def convert_to_pop(ir: HighLevelCall, node: "Node") -> List[Operation]:
def look_for_library_or_top_level(
contract: Contract,
ir: HighLevelCall,
using_for,
t: Union[
@ -1533,10 +1532,12 @@ def look_for_library_or_top_level(
internalcall.lvalue = None
return internalcall
lib_contract = None
if isinstance(destination, FunctionContract) and destination.contract.is_library:
lib_contract = destination.contract
else:
lib_contract = contract.file_scope.get_contract_from_name(str(destination))
elif isinstance(destination, UserDefinedType) and isinstance(destination.type, Contract):
lib_contract = destination.type
if lib_contract:
lib_call = LibraryCall(
lib_contract,
@ -1560,18 +1561,14 @@ def look_for_library_or_top_level(
def convert_to_library_or_top_level(
ir: HighLevelCall, node: "Node", using_for
) -> Optional[Union[LibraryCall, InternalCall,]]:
# We use contract_declarer, because Solidity resolve the library
# before resolving the inheritance.
# Though we could use .contract as libraries cannot be shadowed
contract = node.function.contract_declarer
t = ir.destination.type
if t in using_for:
new_ir = look_for_library_or_top_level(contract, ir, using_for, t)
new_ir = look_for_library_or_top_level(ir, using_for, t)
if new_ir:
return new_ir
if "*" in using_for:
new_ir = look_for_library_or_top_level(contract, ir, using_for, "*")
new_ir = look_for_library_or_top_level(ir, using_for, "*")
if new_ir:
return new_ir
@ -1582,7 +1579,7 @@ def convert_to_library_or_top_level(
and UserDefinedType(node.function.contract) in using_for
):
new_ir = look_for_library_or_top_level(
contract, ir, using_for, UserDefinedType(node.function.contract)
ir, using_for, UserDefinedType(node.function.contract)
)
if new_ir:
return new_ir
@ -1719,6 +1716,7 @@ def convert_type_of_high_and_internal_level_call(
Returns:
Potential new IR
"""
func = None
if isinstance(ir, InternalCall):
candidates: List[Function]
@ -1736,7 +1734,10 @@ def convert_type_of_high_and_internal_level_call(
]
for import_statement in contract.file_scope.imports:
if import_statement.alias and import_statement.alias == ir.contract_name:
if (
import_statement.alias is not None
and import_statement.alias == ir.contract_name
):
imported_scope = contract.compilation_unit.get_scope(import_statement.filename)
candidates += [
f
@ -1767,7 +1768,7 @@ def convert_type_of_high_and_internal_level_call(
if func is None and candidates:
func = _find_function_from_parameter(ir.arguments, candidates, False)
# lowlelvel lookup needs to be done at last step
# low level lookup needs to be done as last step
if not func:
if can_be_low_level(ir):
return convert_to_low_level(ir)
@ -1922,35 +1923,21 @@ def convert_constant_types(irs: List[Operation]) -> None:
while was_changed:
was_changed = False
for ir in irs:
if isinstance(ir, Assignment):
if isinstance(ir.lvalue.type, ElementaryType):
if ir.lvalue.type.type in ElementaryTypeInt:
if isinstance(ir.rvalue, Function):
continue
if isinstance(ir.rvalue, TupleVariable):
# TODO: fix missing Unpack conversion
continue
if isinstance(ir.rvalue.type, TypeAlias):
ir.rvalue.set_type(ElementaryType(ir.lvalue.type.name))
was_changed = True
elif ir.rvalue.type.type not in ElementaryTypeInt:
ir.rvalue.set_type(ElementaryType(ir.lvalue.type.type))
if isinstance(ir, (Assignment, Binary)):
if (
isinstance(ir.lvalue.type, ElementaryType)
and ir.lvalue.type.type in ElementaryTypeInt
):
for r in ir.read:
if isinstance(r, Constant) and r.type.type not in ElementaryTypeInt:
r.set_type(ElementaryType(ir.lvalue.type.type))
was_changed = True
if isinstance(ir, Binary):
if isinstance(ir.lvalue.type, ElementaryType):
if ir.lvalue.type.type in ElementaryTypeInt:
for r in ir.read:
if r.type.type not in ElementaryTypeInt:
r.set_type(ElementaryType(ir.lvalue.type.type))
was_changed = True
if isinstance(ir, (HighLevelCall, InternalCall)):
func = ir.function
if isinstance(func, StateVariable):
types = export_nested_types_from_variable(func)
else:
if func is None:
# TODO: add POP instruction
break
types = [p.type for p in func.parameters]
assert len(types) == len(ir.arguments)
for idx, arg in enumerate(ir.arguments):
@ -1960,6 +1947,7 @@ def convert_constant_types(irs: List[Operation]) -> None:
if arg.type.type not in ElementaryTypeInt:
arg.set_type(ElementaryType(t.type))
was_changed = True
if isinstance(ir, NewStructure):
st = ir.structure
for idx, arg in enumerate(ir.arguments):
@ -1969,11 +1957,15 @@ def convert_constant_types(irs: List[Operation]) -> None:
if arg.type.type not in ElementaryTypeInt:
arg.set_type(ElementaryType(e.type.type))
was_changed = True
def is_elementary_array(t):
return isinstance(t, ArrayType) and isinstance(t.type, ElementaryType)
if isinstance(ir, InitArray):
if isinstance(ir.lvalue.type, ArrayType):
if isinstance(ir.lvalue.type.type, ElementaryType):
if ir.lvalue.type.type.type in ElementaryTypeInt:
for r in ir.read:
if is_elementary_array(ir.lvalue.type):
if ir.lvalue.type.type.type in ElementaryTypeInt:
for r in ir.read:
if isinstance(r, Constant) and is_elementary_array(r.type):
if r.type.type.type not in ElementaryTypeInt:
r.set_type(ElementaryType(ir.lvalue.type.type.type))
was_changed = True
@ -2014,6 +2006,9 @@ def _find_source_mapping_references(irs: List[Operation]) -> None:
if isinstance(ir, NewContract):
ir.contract_created.references.append(ir.expression.source_mapping)
if isinstance(ir, HighLevelCall):
ir.function.references.append(ir.expression.source_mapping)
# endregion
###################################################################################

@ -34,7 +34,6 @@ class Call(Operation):
def arguments(self, v: List[Variable]) -> None:
self._arguments = v
# pylint: disable=no-self-use
def can_reenter(self, _callstack: Optional[List[Union[Function, Variable]]] = None) -> bool:
"""
Must be called after slithIR analysis pass
@ -42,7 +41,7 @@ class Call(Operation):
"""
return False
def can_send_eth(self) -> bool: # pylint: disable=no-self-use
def can_send_eth(self) -> bool:
"""
Must be called after slithIR analysis pass
:return: bool

@ -54,7 +54,6 @@ class HighLevelCall(Call, OperationWithLValue):
# Development function, to be removed once the code is stable
# It is overridden by LibraryCall
# pylint: disable=no-self-use
def _check_destination(self, destination: Union[Variable, SolidityVariable, Contract]) -> None:
assert isinstance(destination, (Variable, SolidityVariable))

@ -1,14 +1,13 @@
from typing import List, Union
from slither.slithir.operations.lvalue import OperationWithLValue
from slither.slithir.utils.utils import is_valid_rvalue
from slither.slithir.variables.constant import Constant
from slither.slithir.utils.utils import is_valid_rvalue, RVALUE
from slither.slithir.variables.temporary import TemporaryVariable
from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA
class InitArray(OperationWithLValue):
def __init__(
self, init_values: List[Constant], lvalue: Union[TemporaryVariableSSA, TemporaryVariable]
self, init_values: List[RVALUE], lvalue: Union[TemporaryVariableSSA, TemporaryVariable]
) -> None:
# init_values can be an array of n dimension
# reduce was removed in py3
@ -30,11 +29,11 @@ class InitArray(OperationWithLValue):
self._lvalue = lvalue
@property
def read(self) -> List[Constant]:
def read(self) -> List[RVALUE]:
return self._unroll(self.init_values)
@property
def init_values(self) -> List[Constant]:
def init_values(self) -> List[RVALUE]:
return list(self._init_values)
def __str__(self):

@ -3,9 +3,9 @@ from typing import List, Union, TYPE_CHECKING
from slither.core.solidity_types.array_type import ArrayType
from slither.slithir.operations.call import Call
from slither.slithir.operations.lvalue import OperationWithLValue
from slither.slithir.utils.utils import RVALUE
if TYPE_CHECKING:
from slither.slithir.variables.constant import Constant
from slither.slithir.variables.temporary import TemporaryVariable
from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA
@ -27,7 +27,7 @@ class NewArray(Call, OperationWithLValue):
return self._array_type
@property
def read(self) -> List["Constant"]:
def read(self) -> List[RVALUE]:
return self._unroll(self.arguments)
def __str__(self):

@ -3,9 +3,9 @@ from typing import Optional, Any, List, Union
from slither.core.declarations import Function
from slither.core.declarations.contract import Contract
from slither.core.variables import Variable
from slither.core.solidity_types import UserDefinedType
from slither.slithir.operations import Call, OperationWithLValue
from slither.slithir.utils.utils import is_valid_lvalue
from slither.slithir.variables.constant import Constant
from slither.slithir.variables.temporary import TemporaryVariable
from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA
@ -13,7 +13,7 @@ from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA
class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instance-attributes
def __init__(
self,
contract_name: Constant,
contract_name: UserDefinedType,
lvalue: Union[TemporaryVariableSSA, TemporaryVariable],
names: Optional[List[str]] = None,
) -> None:
@ -23,7 +23,9 @@ class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instan
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
assert isinstance(contract_name, Constant)
assert isinstance(
contract_name.type, Contract
), f"contract_name is {contract_name} of type {type(contract_name)}"
assert is_valid_lvalue(lvalue)
super().__init__(names=names)
self._contract_name = contract_name
@ -58,7 +60,7 @@ class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instan
self._call_salt = s
@property
def contract_name(self) -> Constant:
def contract_name(self) -> UserDefinedType:
return self._contract_name
@property
@ -69,10 +71,7 @@ class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instan
@property
def contract_created(self) -> Contract:
contract_name = self.contract_name
contract_instance = self.node.file_scope.get_contract_from_name(contract_name)
assert contract_instance
return contract_instance
return self.contract_name.type
###################################################################################
###################################################################################

@ -90,13 +90,13 @@ def transform_slithir_vars_to_ssa(
tmp_variables = [v for v in variables if isinstance(v, TemporaryVariable)]
for idx, _ in enumerate(tmp_variables):
tmp_variables[idx].index = idx
tmp_variables[idx].index = idx # pylint: disable=unnecessary-list-index-lookup
ref_variables = [v for v in variables if isinstance(v, ReferenceVariable)]
for idx, _ in enumerate(ref_variables):
ref_variables[idx].index = idx
ref_variables[idx].index = idx # pylint: disable=unnecessary-list-index-lookup
tuple_variables = [v for v in variables if isinstance(v, TupleVariable)]
for idx, _ in enumerate(tuple_variables):
tuple_variables[idx].index = idx
tuple_variables[idx].index = idx # pylint: disable=unnecessary-list-index-lookup
###################################################################################

@ -1,6 +1,6 @@
import logging
import re
from typing import Any, List, Dict, Callable, TYPE_CHECKING, Union, Set, Sequence
from typing import Any, List, Dict, Callable, TYPE_CHECKING, Union, Set, Sequence, Tuple
from slither.core.declarations import (
Modifier,
@ -9,20 +9,21 @@ from slither.core.declarations import (
StructureContract,
Function,
)
from slither.core.declarations.contract import Contract, USING_FOR_KEY
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.solidity_types import ElementaryType, TypeAliasContract
from slither.core.variables.state_variable import StateVariable
from slither.solc_parsing.declarations.caller_context import CallerContextExpression
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
from slither.solc_parsing.declarations.event import EventSolc
from slither.solc_parsing.declarations.event_contract import EventContractSolc
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.modifier import ModifierSolc
from slither.solc_parsing.declarations.structure_contract import StructureContractSolc
from slither.solc_parsing.exceptions import ParsingError, VariableNotFound
from slither.solc_parsing.solidity_types.type_parsing import parse_type
from slither.solc_parsing.variables.state_variable import StateVariableSolc
from slither.utils.using_for import USING_FOR_KEY
LOGGER = logging.getLogger("ContractSolcParsing")
@ -64,7 +65,8 @@ class ContractSolc(CallerContextExpression):
# use to remap inheritance id
self._remapping: Dict[str, str] = {}
self.baseContracts: List[str] = []
# (referencedDeclaration, offset)
self.baseContracts: List[Tuple[int, str]] = []
self.baseConstructorContractsCalled: List[str] = []
self._linearized_base_contracts: List[int]
@ -174,6 +176,9 @@ class ContractSolc(CallerContextExpression):
self._contract.is_fully_implemented = attributes["fullyImplemented"]
self._linearized_base_contracts = attributes["linearizedBaseContracts"]
if "abstract" in attributes:
self._contract.is_abstract = attributes["abstract"]
# Parse base contract information
self._parse_base_contract_info()
@ -201,7 +206,9 @@ class ContractSolc(CallerContextExpression):
# Obtain our contract reference and add it to our base contract list
referencedDeclaration = base_contract["baseName"]["referencedDeclaration"]
self.baseContracts.append(referencedDeclaration)
self.baseContracts.append(
(referencedDeclaration, base_contract["baseName"]["src"])
)
# If we have defined arguments in our arguments object, this is a constructor invocation.
# (note: 'arguments' can be [], which is not the same as None. [] implies a constructor was
@ -233,7 +240,10 @@ class ContractSolc(CallerContextExpression):
referencedDeclaration = base_contract_items[0]["attributes"][
"referencedDeclaration"
]
self.baseContracts.append(referencedDeclaration)
self.baseContracts.append(
(referencedDeclaration, base_contract_items[0]["src"])
)
# If we have an 'attributes'->'arguments' which is None, this is not a constructor call.
if (
@ -575,7 +585,9 @@ class ContractSolc(CallerContextExpression):
element.is_shadowed = True
accessible_elements[element.full_name].shadows = True
except (VariableNotFound, KeyError) as e:
self.log_incorrect_parsing(f"Missing params {e}")
self.log_incorrect_parsing(
f"Missing params {e} {self._contract.source_mapping.to_detailed_str()}"
)
return all_elements
def analyze_constant_state_variables(self) -> None:
@ -727,7 +739,7 @@ class ContractSolc(CallerContextExpression):
new_enum.set_offset(enum["src"], self._contract.compilation_unit)
self._contract.enums_as_dict[canonicalName] = new_enum
def _analyze_struct(self, struct: StructureContractSolc) -> None: # pylint: disable=no-self-use
def _analyze_struct(self, struct: StructureContractSolc) -> None:
struct.analyze()
def analyze_structs(self) -> None:
@ -751,7 +763,7 @@ class ContractSolc(CallerContextExpression):
event.set_contract(self._contract)
event.set_offset(event_to_parse["src"], self._contract.compilation_unit)
event_parser = EventSolc(event, event_to_parse, self._slither_parser) # type: ignore
event_parser = EventContractSolc(event, event_to_parse, self) # type: ignore
event_parser.analyze() # type: ignore
self._contract.events_as_dict[event.full_name] = event
except (VariableNotFound, KeyError) as e:

@ -1,27 +1,27 @@
"""
Event module
EventContract module
"""
from typing import TYPE_CHECKING, Dict
from slither.core.variables.event_variable import EventVariable
from slither.solc_parsing.variables.event_variable import EventVariableSolc
from slither.core.declarations.event import Event
from slither.core.declarations.event_contract import EventContract
if TYPE_CHECKING:
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.solc_parsing.declarations.contract import ContractSolc
class EventSolc:
class EventContractSolc:
"""
Event class
EventContract class
"""
def __init__(
self, event: Event, event_data: Dict, slither_parser: "SlitherCompilationUnitSolc"
self, event: EventContract, event_data: Dict, contract_parser: "ContractSolc"
) -> None:
self._event = event
self._slither_parser = slither_parser
self._contract_parser = contract_parser
if self.is_compact_ast:
self._event.name = event_data["name"]
@ -42,16 +42,16 @@ class EventSolc:
@property
def is_compact_ast(self) -> bool:
return self._slither_parser.is_compact_ast
return self._contract_parser.is_compact_ast
def analyze(self) -> None:
for elem_to_parse in self._elemsNotParsed:
elem = EventVariable()
# Todo: check if the source offset is always here
if "src" in elem_to_parse:
elem.set_offset(elem_to_parse["src"], self._slither_parser.compilation_unit)
elem.set_offset(elem_to_parse["src"], self._contract_parser.compilation_unit)
elem_parser = EventVariableSolc(elem, elem_to_parse)
elem_parser.analyze(self._slither_parser)
elem_parser.analyze(self._contract_parser)
self._event.elems.append(elem)

@ -0,0 +1,75 @@
"""
EventTopLevel module
"""
from typing import TYPE_CHECKING, Dict
from slither.core.declarations.event_top_level import EventTopLevel
from slither.core.variables.event_variable import EventVariable
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.solc_parsing.variables.event_variable import EventVariableSolc
from slither.solc_parsing.declarations.caller_context import CallerContextExpression
if TYPE_CHECKING:
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
class EventTopLevelSolc(CallerContextExpression):
"""
EventTopLevel class
"""
def __init__(
self, event: EventTopLevel, event_data: Dict, slither_parser: "SlitherCompilationUnitSolc"
) -> None:
self._event = event
self._slither_parser = slither_parser
if self.is_compact_ast:
self._event.name = event_data["name"]
elems = event_data["parameters"]
assert elems["nodeType"] == "ParameterList"
self._elemsNotParsed = elems["parameters"]
else:
self._event.name = event_data["attributes"]["name"]
for elem in event_data["children"]:
# From Solidity 0.6.3 to 0.6.10 (included)
# Comment above a event might be added in the children
# of an event for the legacy ast
if elem["name"] == "ParameterList":
if "children" in elem:
self._elemsNotParsed = elem["children"]
else:
self._elemsNotParsed = []
def analyze(self) -> None:
for elem_to_parse in self._elemsNotParsed:
elem = EventVariable()
# Todo: check if the source offset is always here
if "src" in elem_to_parse:
elem.set_offset(elem_to_parse["src"], self._slither_parser.compilation_unit)
elem_parser = EventVariableSolc(elem, elem_to_parse)
elem_parser.analyze(self)
self._event.elems.append(elem)
self._elemsNotParsed = []
@property
def is_compact_ast(self) -> bool:
return self._slither_parser.is_compact_ast
@property
def compilation_unit(self) -> SlitherCompilationUnit:
return self._slither_parser.compilation_unit
def get_key(self) -> str:
return self._slither_parser.get_key()
@property
def slither_parser(self) -> "SlitherCompilationUnitSolc":
return self._slither_parser
@property
def underlying_event(self) -> EventTopLevel:
return self._event

@ -63,11 +63,14 @@ class FunctionSolc(CallerContextExpression):
# Only present if compact AST
if self.is_compact_ast:
self._function.name = function_data["name"]
if "id" in function_data:
self._function.id = function_data["id"]
else:
self._function.name = function_data["attributes"][self.get_key()]
if "id" in function_data:
self._function.id = function_data["id"]
self._functionNotParsed = function_data
self._returnsNotParsed: List[dict] = []
self._params_was_analyzed = False
self._content_was_analyzed = False
@ -207,8 +210,6 @@ class FunctionSolc(CallerContextExpression):
else:
attributes = self._functionNotParsed["attributes"]
if "payable" in attributes:
self._function.payable = attributes["payable"]
if "stateMutability" in attributes:
if attributes["stateMutability"] == "payable":
self._function.payable = True
@ -242,6 +243,34 @@ class FunctionSolc(CallerContextExpression):
if "payable" in attributes:
self._function.payable = attributes["payable"]
if "baseFunctions" in attributes:
overrides_ids = attributes["baseFunctions"]
if len(overrides_ids) > 0:
for f_id in overrides_ids:
funcs = self.slither_parser.functions_by_id[f_id]
for f in funcs:
# Do not consider leaf contracts as overrides.
# B is A { function a() override {} } and C is A { function a() override {} } override A.a(), not each other.
if (
f.contract == self._function.contract
or f.contract in self._function.contract.inheritance
):
self._function.overrides.append(f)
f.overridden_by.append(self._function)
# Attaches reference to override specifier e.g. X is referenced by `function a() override(X)`
if "overrides" in attributes and isinstance(attributes["overrides"], dict):
for override in attributes["overrides"].get("overrides", []):
refId = override["referencedDeclaration"]
overridden_contract = self.slither_parser.contracts_by_id.get(refId, None)
if overridden_contract:
overridden_contract.add_reference_from_raw_source(
override["src"], self.compilation_unit
)
if "virtual" in attributes:
self._function.is_virtual = attributes["virtual"]
def analyze_params(self) -> None:
# Can be re-analyzed due to inheritance
if self._params_was_analyzed:
@ -280,6 +309,7 @@ class FunctionSolc(CallerContextExpression):
if self.is_compact_ast:
body = self._functionNotParsed.get("body", None)
return_params = self._functionNotParsed.get("returnParameters", None)
if body and body[self.get_key()] == "Block":
self._function.is_implemented = True
@ -290,6 +320,7 @@ class FunctionSolc(CallerContextExpression):
else:
children = self._functionNotParsed[self.get_children("children")]
return_params = children[1]
self._function.is_implemented = False
for child in children[2:]:
if child[self.get_key()] == "Block":
@ -315,6 +346,9 @@ class FunctionSolc(CallerContextExpression):
self._remove_alone_endif()
if return_params:
self._fix_implicit_return(return_params)
if self._function.entry_point:
self._update_reachability(self._function.entry_point)
@ -428,7 +462,7 @@ class FunctionSolc(CallerContextExpression):
return node_endWhile
def _parse_for_compact_ast( # pylint: disable=no-self-use
def _parse_for_compact_ast(
self, statement: Dict
) -> Tuple[Optional[Dict], Optional[Dict], Optional[Dict], Dict]:
body = statement["body"]
@ -637,7 +671,6 @@ class FunctionSolc(CallerContextExpression):
link_underlying_nodes(node_condition, node_endDoWhile)
return node_endDoWhile
# pylint: disable=no-self-use
def _construct_try_expression(self, externalCall: Dict, parameters_list: Dict) -> Dict:
# if the parameters are more than 1 we make the leftHandSide of the Assignment node
# a TupleExpression otherwise an Identifier
@ -818,19 +851,26 @@ class FunctionSolc(CallerContextExpression):
new_node = self._parse_variable_definition_init_tuple(
new_statement, i, new_node, scope
)
else:
variables.append(None)
i = i + 1
var_identifiers = []
# craft of the expression doing the assignement
for v in variables:
identifier = {
"nodeType": "Identifier",
"src": v["src"],
"name": v["name"],
"referencedDeclaration": v["id"],
"typeDescriptions": {"typeString": v["typeDescriptions"]["typeString"]},
}
var_identifiers.append(identifier)
if v is not None:
identifier = {
"nodeType": "Identifier",
"src": v["src"],
"name": v["name"],
"referencedDeclaration": v["id"],
"typeDescriptions": {
"typeString": v["typeDescriptions"]["typeString"]
},
}
var_identifiers.append(identifier)
else:
var_identifiers.append(None)
tuple_expression = {
"nodeType": "TupleExpression",
@ -1165,16 +1205,38 @@ class FunctionSolc(CallerContextExpression):
return None
def _find_start_loop(self, node: Node, visited: List[Node]) -> Optional[Node]:
def _find_if_loop(self, node: Node, visited: List[Node], skip_if_loop: int) -> Optional[Node]:
if node in visited:
return None
# If skip_if_loop is not 0 it means we are in a nested situation
# and we have to skip the closer n loop headers.
# If in the fathers there is an EXPRESSION node it's a for loop
# and it's the index increment expression
if node.type == NodeType.IFLOOP:
if skip_if_loop == 0:
for father in node.fathers:
if father.type == NodeType.EXPRESSION:
return father
return node
# skip_if_loop works as explained above.
# This handle when a for loop doesn't have a condition
# e.g. for (;;) {}
# and decrement skip_if_loop since we are out of the nested loop
if node.type == NodeType.STARTLOOP:
return node
if skip_if_loop == 0:
return node
skip_if_loop -= 1
# If we find an ENDLOOP means we are in a nested loop
# we increment skip_if_loop counter
if node.type == NodeType.ENDLOOP:
skip_if_loop += 1
visited = visited + [node]
for father in node.fathers:
ret = self._find_start_loop(father, visited)
ret = self._find_if_loop(father, visited, skip_if_loop)
if ret:
return ret
@ -1197,15 +1259,22 @@ class FunctionSolc(CallerContextExpression):
end_node.add_father(node)
def _fix_continue_node(self, node: Node) -> None:
start_node = self._find_start_loop(node, [])
if_loop_node = self._find_if_loop(node, [], 0)
if not start_node:
if not if_loop_node:
raise ParsingError(f"Continue in no-loop context {node.node_id}")
for son in node.sons:
son.remove_father(node)
node.set_sons([start_node])
start_node.add_father(node)
node.set_sons([if_loop_node])
if_loop_node.add_father(node)
# endregion
###################################################################################
###################################################################################
# region Try-Catch
###################################################################################
###################################################################################
def _fix_try(self, node: Node) -> None:
end_node = next((son for son in node.sons if son.type != NodeType.CATCH), None)
@ -1223,6 +1292,13 @@ class FunctionSolc(CallerContextExpression):
visited.add(son)
self._fix_catch(son, end_node, visited)
# endregion
###################################################################################
###################################################################################
# region Params, Returns, Modifiers
###################################################################################
###################################################################################
def _add_param(self, param: Dict, initialized: bool = False) -> LocalVariableSolc:
local_var = LocalVariable()
@ -1276,11 +1352,11 @@ class FunctionSolc(CallerContextExpression):
self._function.returns_src().set_offset(returns["src"], self._function.compilation_unit)
if self.is_compact_ast:
returns = returns["parameters"]
self._returnsNotParsed = returns["parameters"]
else:
returns = returns[self.get_children("children")]
self._returnsNotParsed = returns[self.get_children("children")]
for ret in returns:
for ret in self._returnsNotParsed:
assert ret[self.get_key()] == "VariableDeclaration"
local_var = self._add_param(ret)
self._function.add_return(local_var.underlying_variable)
@ -1334,6 +1410,138 @@ class FunctionSolc(CallerContextExpression):
)
)
def _fix_implicit_return(self, return_params: Dict) -> None:
"""
Creates an artificial return node iff a function has a named return variable declared in its signature.
Finds all leaf nodes in the CFG which are not return nodes, and links them to the artificial return node.
"""
does_not_have_return_params = len(self.underlying_function.returns) == 0
does_not_have_named_returns = all(
ret.name == "" for ret in self.underlying_function.returns
)
not_implemented = not self._function.is_implemented
if does_not_have_return_params or does_not_have_named_returns or not_implemented:
return
return_node = self._new_node(
NodeType.RETURN, return_params["src"], self.underlying_function
)
for node, node_solc in self._node_to_nodesolc.items():
if len(node.sons) == 0 and node.type not in [NodeType.RETURN, NodeType.THROW]:
link_underlying_nodes(node_solc, return_node)
for _, yul_block in self._node_to_yulobject.items():
for yul_node in yul_block.nodes:
node = yul_node.underlying_node
if len(node.sons) == 0 and node.type not in [NodeType.RETURN, NodeType.THROW]:
link_underlying_nodes(yul_node, return_node)
if self.is_compact_ast:
self._add_return_exp_compact(return_node, return_params)
else:
self._add_return_exp_legacy(return_node, return_params)
return_node.analyze_expressions(self)
def _add_return_exp_compact(self, return_node: NodeSolc, return_params: Dict) -> None:
if len(self.underlying_function.returns) == 1:
return_arg = self.underlying_function.returns[0]
if return_arg.name != "":
(refId, refSrc, refType) = next(
(ret["id"], ret["src"], ret["typeDescriptions"])
for ret in self._returnsNotParsed
if ret["name"] == return_arg.name
)
return_node.add_unparsed_expression(
{
"name": return_arg.name,
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": refId,
"src": refSrc,
"typeDescriptions": refType,
}
)
else:
expression = {
"components": [],
"isConstant": False,
"isInlineArray": False,
"isLValue": False,
"isPure": False,
"lValueRequested": False,
"nodeType": "TupleExpression",
"src": return_params["src"],
"typeDescriptions": {},
}
type_ids = []
type_strs = []
for return_arg in self.underlying_function.returns:
# For each named return variable, we add an identifier to the tuple.
if return_arg.name != "":
(refId, refSrc, refType) = next(
(ret["id"], ret["src"], ret["typeDescriptions"])
for ret in self._returnsNotParsed
if ret["name"] == return_arg.name
)
type_ids.append(refType["typeIdentifier"])
type_strs.append(refType["typeString"])
expression["components"].append(
{
"name": return_arg.name,
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": refId,
"src": refSrc,
"typeDescriptions": refType,
}
)
expression["typeDescriptions"]["typeIdentifier"] = (
"t_tuple$_" + "_$_".join(type_ids) + "_$"
)
expression["typeDescriptions"]["typeString"] = "tuple(" + ",".join(type_strs) + ")"
return_node.add_unparsed_expression(expression)
def _add_return_exp_legacy(self, return_node: NodeSolc, return_params: Dict) -> None:
if len(self.underlying_function.returns) == 1:
return_arg = self.underlying_function.returns[0]
if return_arg.name != "":
(refSrc, refType) = next(
(ret["src"], ret["attributes"]["type"])
for ret in self._returnsNotParsed
if ret["attributes"]["name"] == return_arg.name
)
return_node.add_unparsed_expression(
{
"attributes": {"type": refType, "value": return_arg.name},
"name": "Identifier",
"src": refSrc,
}
)
else:
expression = {
"children": [],
"name": "TupleExpression",
"src": return_params["src"],
}
for return_arg in self.underlying_function.returns:
# For each named return variable, we add an identifier to the tuple.
if return_arg.name != "":
(refSrc, refType) = next(
(ret["src"], ret["attributes"]["type"])
for ret in self._returnsNotParsed
if ret["attributes"]["name"] == return_arg.name
)
expression["children"].append(
{
"attributes": {"type": refType, "value": return_arg.name},
"name": "Identifier",
"src": refSrc,
}
)
return_node.add_unparsed_expression(expression)
# endregion
###################################################################################
###################################################################################

@ -614,20 +614,9 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
assert type_name[caller_context.get_key()] == "UserDefinedTypeName"
if is_compact_ast:
# Changed introduced in Solidity 0.8
# see https://github.com/crytic/slither/issues/794
# TODO explore more the changes introduced in 0.8 and the usage of pathNode/IdentifierPath
if "name" not in type_name:
assert "pathNode" in type_name and "name" in type_name["pathNode"]
contract_name = type_name["pathNode"]["name"]
else:
contract_name = type_name["name"]
else:
contract_name = type_name["attributes"]["name"]
new = NewContract(contract_name)
contract_type = parse_type(type_name, caller_context)
assert isinstance(contract_type, UserDefinedType)
new = NewContract(contract_type)
new.set_offset(src, caller_context.compilation_unit)
return new

@ -134,8 +134,9 @@ def find_top_level(
if var_name in scope.enums:
return scope.enums[var_name], False
if var_name in scope.events:
return scope.events[var_name], False
for event in scope.events:
if var_name == event.full_name:
return event, False
for import_directive in scope.imports:
if import_directive.alias == var_name:
@ -268,6 +269,7 @@ def _find_variable_init(
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc
from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc
from slither.solc_parsing.declarations.event_top_level import EventTopLevelSolc
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
direct_contracts: List[Contract]
@ -303,6 +305,7 @@ def _find_variable_init(
else:
assert isinstance(underlying_function, FunctionContract)
scope = underlying_function.contract.file_scope
elif isinstance(caller_context, StructureTopLevelSolc):
direct_contracts = []
direct_functions_parser = []
@ -311,6 +314,10 @@ def _find_variable_init(
direct_contracts = []
direct_functions_parser = []
scope = caller_context.underlying_variable.file_scope
elif isinstance(caller_context, EventTopLevelSolc):
direct_contracts = []
direct_functions_parser = []
scope = caller_context.underlying_event.file_scope
elif isinstance(caller_context, CustomErrorSolc):
if caller_context.contract_parser:
direct_contracts = [caller_context.contract_parser.underlying_contract]
@ -499,4 +506,6 @@ def find_variable(
if ret:
return ret, False
raise VariableNotFound(f"Variable not found: {var_name} (context {contract})")
raise VariableNotFound(
f"Variable not found: {var_name} (context {contract} {contract.source_mapping.to_detailed_str()})"
)

@ -1,3 +1,4 @@
from collections import defaultdict
import json
import logging
import os
@ -7,7 +8,7 @@ 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 import Contract, Function
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.event_top_level import EventTopLevel
@ -24,7 +25,7 @@ from slither.solc_parsing.declarations.caller_context import CallerContextExpres
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.event import EventSolc
from slither.solc_parsing.declarations.event_top_level import EventTopLevelSolc
from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc
from slither.solc_parsing.declarations.using_for_top_level import UsingForTopLevelSolc
from slither.solc_parsing.exceptions import VariableNotFound
@ -73,13 +74,23 @@ def _handle_import_aliases(
class SlitherCompilationUnitSolc(CallerContextExpression):
# pylint: disable=no-self-use,too-many-instance-attributes
# pylint: disable=too-many-instance-attributes
def __init__(self, compilation_unit: SlitherCompilationUnit) -> None:
super().__init__()
self._compilation_unit: SlitherCompilationUnit = compilation_unit
self._contracts_by_id: Dict[int, ContractSolc] = {}
self._contracts_by_id: Dict[int, Contract] = {}
# For top level functions, there should only be one `Function` since they can't be virtual and therefore can't be overridden.
self._functions_by_id: Dict[int, List[Function]] = defaultdict(list)
self.imports_by_id: Dict[int, Import] = {}
self.top_level_events_by_id: Dict[int, EventTopLevel] = {}
self.top_level_errors_by_id: Dict[int, EventTopLevel] = {}
self.top_level_structures_by_id: Dict[int, StructureTopLevel] = {}
self.top_level_variables_by_id: Dict[int, TopLevelVariable] = {}
self.top_level_type_aliases_by_id: Dict[int, TypeAliasTopLevel] = {}
self.top_level_enums_by_id: Dict[int, EnumTopLevel] = {}
self._parsed = False
self._analyzed = False
self._is_compact_ast = False
@ -90,6 +101,7 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
self._variables_top_level_parser: List[TopLevelVariableSolc] = []
self._functions_top_level_parser: List[FunctionSolc] = []
self._using_for_top_level_parser: List[UsingForTopLevelSolc] = []
self._events_top_level_parser: List[EventTopLevelSolc] = []
self._all_functions_and_modifier_parser: List[FunctionSolc] = []
self._top_level_contracts_counter = 0
@ -104,6 +116,7 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
def add_function_or_modifier_parser(self, f: FunctionSolc) -> None:
self._all_functions_and_modifier_parser.append(f)
self._functions_by_id[f.underlying_function.id].append(f.underlying_function)
@property
def underlying_contract_to_parser(self) -> Dict[Contract, ContractSolc]:
@ -113,6 +126,14 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
def slither_parser(self) -> "SlitherCompilationUnitSolc":
return self
@property
def contracts_by_id(self) -> Dict[int, Contract]:
return self._contracts_by_id
@property
def functions_by_id(self) -> Dict[int, List[Function]]:
return self._functions_by_id
###################################################################################
###################################################################################
# region AST
@ -192,9 +213,11 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
scope = self.compilation_unit.get_scope(filename)
enum = EnumTopLevel(name, canonicalName, values, scope)
scope.enums[name] = enum
enum.set_offset(top_level_data["src"], self._compilation_unit)
self._compilation_unit.enums_top_level.append(enum)
scope.enums[name] = enum
refId = top_level_data["id"]
self.top_level_enums_by_id[refId] = enum
# pylint: disable=too-many-branches,too-many-statements,too-many-locals
def parse_top_level_items(self, data_loaded: Dict, filename: str) -> None:
@ -205,8 +228,13 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
)
return
exported_symbols = {}
if "nodeType" in data_loaded:
self._is_compact_ast = True
exported_symbols = data_loaded.get("exportedSymbols", {})
else:
attributes = data_loaded.get("attributes", {})
exported_symbols = attributes.get("exportedSymbols", {})
if "sourcePaths" in data_loaded:
for sourcePath in data_loaded["sourcePaths"]:
@ -224,7 +252,12 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
if self.get_children() not in data_loaded:
return
scope = self.compilation_unit.get_scope(filename)
# Exported symbols includes a reference ID to all top-level definitions the file exports,
# including def's brought in by imports (even transitively) and def's local to the file.
for refId in exported_symbols.values():
scope.exported_symbols |= set(refId)
for top_level_data in data_loaded[self.get_children()]:
if top_level_data[self.get_key()] == "ContractDefinition":
@ -257,6 +290,7 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
self._using_for_top_level_parser.append(usingFor_parser)
elif top_level_data[self.get_key()] == "ImportDirective":
referenceId = top_level_data["id"]
if self.is_compact_ast:
import_directive = Import(
Path(
@ -287,6 +321,7 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
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)
self.imports_by_id[referenceId] = import_directive
get_imported_scope = self.compilation_unit.get_scope(import_directive.filename)
scope.accessible_scopes.append(get_imported_scope)
@ -299,6 +334,8 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
self._compilation_unit.structures_top_level.append(st)
self._structures_top_level_parser.append(st_parser)
referenceId = top_level_data["id"]
self.top_level_structures_by_id[referenceId] = st
elif top_level_data[self.get_key()] == "EnumDefinition":
# Note enum don't need a complex parser, so everything is directly done
@ -312,6 +349,9 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
self._compilation_unit.variables_top_level.append(var)
self._variables_top_level_parser.append(var_parser)
scope.variables[var.name] = var
referenceId = top_level_data["id"]
self.top_level_variables_by_id[referenceId] = var
elif top_level_data[self.get_key()] == "FunctionDefinition":
func = FunctionTopLevel(self._compilation_unit, scope)
scope.functions.add(func)
@ -330,6 +370,8 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
scope.custom_errors.add(custom_error)
self._compilation_unit.custom_errors.append(custom_error)
self._custom_error_parser.append(custom_error_parser)
referenceId = top_level_data["id"]
self.top_level_errors_by_id[referenceId] = custom_error
elif top_level_data[self.get_key()] == "UserDefinedValueTypeDefinition":
assert "name" in top_level_data
@ -348,15 +390,19 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
type_alias.set_offset(top_level_data["src"], self._compilation_unit)
self._compilation_unit.type_aliases[alias] = type_alias
scope.type_aliases[alias] = type_alias
referenceId = top_level_data["id"]
self.top_level_type_aliases_by_id[referenceId] = type_alias
elif top_level_data[self.get_key()] == "EventDefinition":
event = EventTopLevel(scope)
event.set_offset(top_level_data["src"], self._compilation_unit)
event_parser = EventSolc(event, top_level_data, self) # type: ignore
event_parser.analyze() # type: ignore
scope.events[event.full_name] = event
event_parser = EventTopLevelSolc(event, top_level_data, self) # type: ignore
self._events_top_level_parser.append(event_parser)
scope.events.add(event)
self._compilation_unit.events_top_level.append(event)
referenceId = top_level_data["id"]
self.top_level_events_by_id[referenceId] = event
else:
raise SlitherException(f"Top level {top_level_data[self.get_key()]} not supported")
@ -417,21 +463,9 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
f"No contracts were found in {self._compilation_unit.core.filename}, check the correct compilation"
)
if self._parsed:
# pylint: disable=broad-exception-raised
raise Exception("Contract analysis can be run only once!")
# First we save all the contracts in a dict
# the key is the contractid
for contract in self._underlying_contract_to_parser:
if contract.name.startswith("SlitherInternalTopLevelContract"):
raise SlitherException(
# region multi-line-string
"""Your codebase has a contract named 'SlitherInternalTopLevelContract'.
Please rename it, this name is reserved for Slither's internals"""
# endregion multi-line
)
self._contracts_by_id[contract.id] = contract
self._compilation_unit.contracts.append(contract)
def resolve_remapping_and_renaming(contract_parser: ContractSolc, want: str) -> Contract:
contract_name = contract_parser.remapping[want]
target = None
@ -478,13 +512,16 @@ Please rename it, this name is reserved for Slither's internals"""
else:
missing_inheritance = i
# Resolve immediate base contracts.
for i in contract_parser.baseContracts:
# Resolve immediate base contracts and attach references.
for (i, src) in contract_parser.baseContracts:
if i in contract_parser.remapping:
target = resolve_remapping_and_renaming(contract_parser, i)
fathers.append(target)
target.add_reference_from_raw_source(src, self.compilation_unit)
elif i in self._contracts_by_id:
fathers.append(self._contracts_by_id[i])
target = self._contracts_by_id[i]
fathers.append(target)
target.add_reference_from_raw_source(src, self.compilation_unit)
else:
missing_inheritance = i
@ -611,6 +648,7 @@ Please rename it, this name is reserved for Slither's internals"""
self._analyze_top_level_variables()
self._analyze_top_level_structures()
self._analyze_top_level_events()
# Start with the contracts without inheritance
# Analyze a contract only if all its fathers
@ -721,6 +759,13 @@ Please rename it, this name is reserved for Slither's internals"""
except (VariableNotFound, KeyError) as e:
raise SlitherException(f"Missing {e} during variable analyze") from e
def _analyze_top_level_events(self) -> None:
try:
for event in self._events_top_level_parser:
event.analyze()
except (VariableNotFound, KeyError) as e:
raise SlitherException(f"Missing event {e} during top level event analyze") from e
def _analyze_params_top_level_function(self) -> None:
for func_parser in self._functions_top_level_parser:
func_parser.analyze_params()

@ -232,12 +232,13 @@ def parse_type(
from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc
from slither.solc_parsing.declarations.event_top_level import EventTopLevelSolc
sl: "SlitherCompilationUnit"
renaming: Dict[str, str]
type_aliases: Dict[str, TypeAlias]
enums_direct_access: List["Enum"] = []
# Note: for convenicence top level functions use the same parser than function in contract
# Note: for convenience top level functions use the same parser as function in contract
# but contract_parser is set to None
if isinstance(caller_context, SlitherCompilationUnitSolc) or (
isinstance(caller_context, FunctionSolc) and caller_context.contract_parser is None
@ -266,7 +267,13 @@ def parse_type(
functions = []
elif isinstance(
caller_context,
(StructureTopLevelSolc, CustomErrorSolc, TopLevelVariableSolc, UsingForTopLevelSolc),
(
StructureTopLevelSolc,
CustomErrorSolc,
TopLevelVariableSolc,
UsingForTopLevelSolc,
EventTopLevelSolc,
),
):
if isinstance(caller_context, StructureTopLevelSolc):
scope = caller_context.underlying_structure.file_scope
@ -274,6 +281,8 @@ def parse_type(
scope = caller_context.underlying_variable.file_scope
elif isinstance(caller_context, UsingForTopLevelSolc):
scope = caller_context.underlying_using_for.file_scope
elif isinstance(caller_context, EventTopLevelSolc):
scope = caller_context.underlying_event.file_scope
else:
assert isinstance(caller_context, CustomErrorSolc)
custom_error = caller_context.underlying_custom_error
@ -304,28 +313,28 @@ def parse_type(
sl = caller_context.compilation_unit
if isinstance(caller_context, FunctionSolc):
underlying_func = caller_context.underlying_function
# If contract_parser is set to None, then underlying_function is a functionContract
# If contract_parser is set to None, then underlying_function is a FunctionContract
# See note above
assert isinstance(underlying_func, FunctionContract)
contract = underlying_func.contract
next_context = caller_context.contract_parser
scope = caller_context.underlying_function.file_scope
scope = underlying_func.file_scope
else:
contract = caller_context.underlying_contract
next_context = caller_context
scope = caller_context.underlying_contract.file_scope
scope = contract.file_scope
structures_direct_access = contract.structures
structures_direct_access += contract.file_scope.structures.values()
all_structuress = [c.structures for c in contract.file_scope.contracts.values()]
structures_direct_access += scope.structures.values()
all_structuress = [c.structures for c in scope.contracts.values()]
all_structures = [item for sublist in all_structuress for item in sublist]
all_structures += contract.file_scope.structures.values()
all_structures += scope.structures.values()
enums_direct_access += contract.enums
enums_direct_access += contract.file_scope.enums.values()
all_enumss = [c.enums for c in contract.file_scope.contracts.values()]
enums_direct_access += scope.enums.values()
all_enumss = [c.enums for c in scope.contracts.values()]
all_enums = [item for sublist in all_enumss for item in sublist]
all_enums += contract.file_scope.enums.values()
contracts = contract.file_scope.contracts.values()
all_enums += scope.enums.values()
contracts = scope.contracts.values()
functions = contract.functions + contract.modifiers
renaming = scope.renaming
@ -486,4 +495,4 @@ def parse_type(
return FunctionType(params_vars, return_values_vars)
raise ParsingError("Type name not found " + str(t))
raise ParsingError(f"Type name not found {(t)} in {scope.filename}")

@ -321,6 +321,10 @@ class YulBlock(YulScope):
def function(self) -> Function:
return self._parent_func
@property
def nodes(self) -> List[YulNode]:
return self._nodes
def new_node(self, node_type: NodeType, src: Union[str, Dict]) -> YulNode:
if self._parent_func:
node = self._parent_func.new_node(node_type, src, self.node_scope)

@ -2,15 +2,24 @@
`slither-mutate` is a mutation testing tool for solidity based smart contracts.
## Usage
## Quick Start
`slither-mutate <codebase> --test-cmd <test-command> <options>`
In a foundry project with **passing** tests that are run with `forge test`, you can mutate all solidity files in the `src` folder by running the following:
To view the list of mutators available `slither-mutate --list-mutators`
`slither-mutate src --test-cmd='forge test'`
You can provide flags to `forge` as part of the `--test-cmd` parameter or target a single file path instead of the whole `src` directory, for example:
`slither-mutate src/core/MyContract.sol --test-cmd='forge test --match-contract="MyContract"'`
### CLI Interface
```shell
$ slither-mutate --help
usage: slither-mutate <codebase> --test-cmd <test command> <options>
Experimental smart contract mutator. Based on https://arxiv.org/abs/2006.11597
positional arguments:
codebase Codebase to analyze (.sol file, project directory, ...)
@ -24,10 +33,11 @@ options:
--timeout TIMEOUT Set timeout for test command (by default 30 seconds)
--output-dir OUTPUT_DIR
Name of output directory (by default 'mutation_campaign')
--verbose output all mutants generated
-v, --verbose log mutants that are caught as well as those that are uncaught
-vv, --very-verbose log mutants that are caught, uncaught, and fail to compile. And more!
--mutators-to-run MUTATORS_TO_RUN
mutant generators to run
--contract-names CONTRACT_NAMES
list of contract names you want to mutate
--quick to stop full mutation if revert mutator passes
--comprehensive continue testing minor mutations if severe mutants are uncaught
```

@ -1,14 +1,17 @@
import argparse
import inspect
import logging
import sys
import os
import shutil
import sys
import time
from pathlib import Path
from typing import Type, List, Any, Optional
from crytic_compile import cryticparser
from slither import Slither
from slither.tools.mutator.utils.testing_generated_mutant import run_test_cmd
from slither.tools.mutator.mutators import all_mutators
from slither.utils.colors import yellow, magenta
from slither.utils.colors import blue, green, magenta, red
from .mutators.abstract_mutator import AbstractMutator
from .utils.command_line import output_mutators
from .utils.file_handling import (
@ -67,8 +70,18 @@ def parse_args() -> argparse.Namespace:
# to print just all the mutants
parser.add_argument(
"-v",
"--verbose",
help="output all mutants generated",
help="log mutants that are caught as well as those that are uncaught",
action="store_true",
default=False,
)
# to print just all the mutants
parser.add_argument(
"-vv",
"--very-verbose",
help="log mutants that are caught, uncaught, and fail to compile. And more!",
action="store_true",
default=False,
)
@ -87,8 +100,8 @@ def parse_args() -> argparse.Namespace:
# flag to run full mutation based revert mutator output
parser.add_argument(
"--quick",
help="to stop full mutation if revert mutator passes",
"--comprehensive",
help="continue testing minor mutations if severe mutants are uncaught",
action="store_true",
default=False,
)
@ -135,7 +148,7 @@ class ListMutators(argparse.Action): # pylint: disable=too-few-public-methods
###################################################################################
def main() -> (None): # pylint: disable=too-many-statements,too-many-branches,too-many-locals
def main() -> None: # pylint: disable=too-many-statements,too-many-branches,too-many-locals
args = parse_args()
# arguments
@ -146,31 +159,32 @@ def main() -> (None): # pylint: disable=too-many-statements,too-many-branches,t
timeout: Optional[int] = args.timeout
solc_remappings: Optional[str] = args.solc_remaps
verbose: Optional[bool] = args.verbose
very_verbose: Optional[bool] = args.very_verbose
mutators_to_run: Optional[List[str]] = args.mutators_to_run
contract_names: Optional[List[str]] = args.contract_names
quick_flag: Optional[bool] = args.quick
comprehensive_flag: Optional[bool] = args.comprehensive
logger.info(magenta(f"Starting Mutation Campaign in '{args.codebase} \n"))
logger.info(blue(f"Starting mutation campaign in {args.codebase}"))
if paths_to_ignore:
paths_to_ignore_list = paths_to_ignore.strip("][").split(",")
logger.info(magenta(f"Ignored paths - {', '.join(paths_to_ignore_list)} \n"))
logger.info(blue(f"Ignored paths - {', '.join(paths_to_ignore_list)}"))
else:
paths_to_ignore_list = []
contract_names: List[str] = []
if args.contract_names:
contract_names = args.contract_names.split(",")
# get all the contracts as a list from given codebase
sol_file_list: List[str] = get_sol_file_list(args.codebase, paths_to_ignore_list)
sol_file_list: List[str] = get_sol_file_list(Path(args.codebase), paths_to_ignore_list)
# folder where backup files and valid mutants created
# folder where backup files and uncaught mutants are saved
if output_dir is None:
output_dir = "/mutation_campaign"
output_folder = os.getcwd() + output_dir
if os.path.exists(output_folder):
shutil.rmtree(output_folder)
output_dir = "./mutation_campaign"
# set default timeout
if timeout is None:
timeout = 30
output_folder = Path(output_dir).resolve()
if output_folder.is_dir():
shutil.rmtree(output_folder)
# setting RR mutator as first mutator
mutators_list = _get_mutators(mutators_to_run)
@ -187,51 +201,138 @@ def main() -> (None): # pylint: disable=too-many-statements,too-many-branches,t
CR_RR_list.insert(1, M)
mutators_list = CR_RR_list + mutators_list
logger.info(blue("Timing tests.."))
# run and time tests, abort if they're broken
start_time = time.time()
# no timeout or target_file during the first run, but be verbose on failure
if not run_test_cmd(test_command, None, None, True):
logger.error(red("Test suite fails with mutations, aborting"))
return
elapsed_time = round(time.time() - start_time)
# set default timeout
# default to twice as long as it usually takes to run the test suite
if timeout is None:
timeout = int(elapsed_time * 2)
else:
timeout = int(timeout)
if timeout < elapsed_time:
logger.info(
red(
f"Provided timeout {timeout} is too short for tests that run in {elapsed_time} seconds"
)
)
return
logger.info(
green(
f"Test suite passes in {elapsed_time} seconds, commencing mutation campaign with a timeout of {timeout} seconds\n"
)
)
# Keep a list of all already mutated contracts so we don't mutate them twice
mutated_contracts: List[str] = []
for filename in sol_file_list: # pylint: disable=too-many-nested-blocks
contract_name = os.path.split(filename)[1].split(".sol")[0]
file_name = os.path.split(filename)[1].split(".sol")[0]
# slither object
sl = Slither(filename, **vars(args))
# create a backup files
files_dict = backup_source_file(sl.source_code, output_folder)
# total count of mutants
total_count = 0
# count of valid mutants
v_count = 0
# total revert/comment/tweak mutants that were generated and compiled
total_mutant_counts = [0, 0, 0]
# total uncaught revert/comment/tweak mutants
uncaught_mutant_counts = [0, 0, 0]
# lines those need not be mutated (taken from RR and CR)
dont_mutate_lines = []
# mutation
target_contract = "SLITHER_SKIP_MUTATIONS" if contract_names else ""
try:
for compilation_unit_of_main_file in sl.compilation_units:
contract_instance = ""
for contract in compilation_unit_of_main_file.contracts:
if contract_names is not None and contract.name in contract_names:
contract_instance = contract
elif str(contract.name).lower() == contract_name.lower():
contract_instance = contract
if contract_instance == "":
logger.error("Can't find the contract")
else:
for M in mutators_list:
m = M(
compilation_unit_of_main_file,
int(timeout),
test_command,
test_directory,
contract_instance,
solc_remappings,
verbose,
output_folder,
dont_mutate_lines,
)
(count_valid, count_invalid, lines_list) = m.mutate()
v_count += count_valid
total_count += count_valid + count_invalid
dont_mutate_lines = lines_list
if not quick_flag:
dont_mutate_lines = []
if contract.name in contract_names and contract.name not in mutated_contracts:
target_contract = contract
break
if not contract_names and contract.name.lower() == file_name.lower():
target_contract = contract
break
if target_contract == "":
logger.info(
f"Cannot find contracts in file {filename}, try specifying them with --contract-names"
)
continue
if target_contract == "SLITHER_SKIP_MUTATIONS":
logger.debug(f"Skipping mutations in {filename}")
continue
# TODO: find a more specific way to omit interfaces
# Ideally, we wouldn't depend on naming conventions
if target_contract.name.startswith("I"):
logger.debug(f"Skipping mutations on interface {filename}")
continue
# Add our target to the mutation list
mutated_contracts.append(target_contract.name)
logger.info(blue(f"Mutating contract {target_contract}"))
for M in mutators_list:
m = M(
compilation_unit_of_main_file,
int(timeout),
test_command,
test_directory,
target_contract,
solc_remappings,
verbose,
very_verbose,
output_folder,
dont_mutate_lines,
)
(total_counts, uncaught_counts, lines_list) = m.mutate()
if m.NAME == "RR":
total_mutant_counts[0] += total_counts[0]
uncaught_mutant_counts[0] += uncaught_counts[0]
if verbose:
logger.info(
magenta(
f"Mutator {m.NAME} found {uncaught_counts[0]} uncaught revert mutants (out of {total_counts[0]} that compile)"
)
)
elif m.NAME == "CR":
total_mutant_counts[1] += total_counts[1]
uncaught_mutant_counts[1] += uncaught_counts[1]
if verbose:
logger.info(
magenta(
f"Mutator {m.NAME} found {uncaught_counts[1]} uncaught comment mutants (out of {total_counts[1]} that compile)"
)
)
else:
total_mutant_counts[2] += total_counts[2]
uncaught_mutant_counts[2] += uncaught_counts[2]
if verbose:
logger.info(
magenta(
f"Mutator {m.NAME} found {uncaught_counts[2]} uncaught tweak mutants (out of {total_counts[2]} that compile)"
)
)
logger.info(
magenta(
f"Running total: found {uncaught_mutant_counts[2]} uncaught tweak mutants (out of {total_mutant_counts[2]} that compile)"
)
)
dont_mutate_lines = lines_list
if comprehensive_flag:
dont_mutate_lines = []
except Exception as e: # pylint: disable=broad-except
logger.error(e)
transfer_and_delete(files_dict)
except KeyboardInterrupt:
# transfer and delete the backup files if interrupted
@ -241,14 +342,59 @@ def main() -> (None): # pylint: disable=too-many-statements,too-many-branches,t
# transfer and delete the backup files
transfer_and_delete(files_dict)
# output
logger.info(
yellow(
f"Done mutating, '{filename}'. Valid mutant count: '{v_count}' and Total mutant count '{total_count}'.\n"
# log results for this file
logger.info(blue(f"Done mutating {target_contract}."))
if total_mutant_counts[0] > 0:
logger.info(
magenta(
f"Revert mutants: {uncaught_mutant_counts[0]} uncaught of {total_mutant_counts[0]} ({100 * uncaught_mutant_counts[0]/total_mutant_counts[0]}%)"
)
)
)
else:
logger.info(magenta("Zero Revert mutants analyzed"))
if total_mutant_counts[1] > 0:
logger.info(
magenta(
f"Comment mutants: {uncaught_mutant_counts[1]} uncaught of {total_mutant_counts[1]} ({100 * uncaught_mutant_counts[1]/total_mutant_counts[1]}%)"
)
)
else:
logger.info(magenta("Zero Comment mutants analyzed"))
if total_mutant_counts[2] > 0:
logger.info(
magenta(
f"Tweak mutants: {uncaught_mutant_counts[2]} uncaught of {total_mutant_counts[2]} ({100 * uncaught_mutant_counts[2]/total_mutant_counts[2]}%)\n"
)
)
else:
logger.info(magenta("Zero Tweak mutants analyzed\n"))
# Reset mutant counts before moving on to the next file
if very_verbose:
logger.info(blue("Reseting mutant counts to zero"))
total_mutant_counts[0] = 0
total_mutant_counts[1] = 0
total_mutant_counts[2] = 0
uncaught_mutant_counts[0] = 0
uncaught_mutant_counts[1] = 0
uncaught_mutant_counts[2] = 0
# Print the total time elapsed in a human-readable time format
elapsed_time = round(time.time() - start_time)
hours, remainder = divmod(elapsed_time, 3600)
minutes, seconds = divmod(remainder, 60)
if hours > 0:
elapsed_string = f"{hours} {'hour' if hours == 1 else 'hours'}"
elif minutes > 0:
elapsed_string = f"{minutes} {'minute' if minutes == 1 else 'minutes'}"
else:
elapsed_string = f"{seconds} {'second' if seconds == 1 else 'seconds'}"
logger.info(magenta(f"Finished Mutation Campaign in '{args.codebase}' \n"))
logger.info(
blue(f"Finished mutation testing assessment of '{args.codebase}' in {elapsed_string}\n")
)
# endregion

@ -24,7 +24,7 @@ class RR(AbstractMutator): # pylint: disable=too-few-public-methods
old_str = self.in_file_str[start:stop]
line_no = node.source_mapping.lines
if not line_no[0] in self.dont_mutate_line:
if old_str != "revert()":
if not old_str.lstrip().startswith("revert"):
new_str = "revert()"
create_patch_with_line(
result,

@ -1,10 +1,10 @@
import abc
import logging
from pathlib import Path
from typing import Optional, Dict, Tuple, List
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.formatters.utils.patches import apply_patch, create_diff
from slither.tools.mutator.utils.testing_generated_mutant import test_patch
from slither.utils.colors import yellow
from slither.core.declarations import Contract
logger = logging.getLogger("Slither-Mutate")
@ -19,8 +19,6 @@ class AbstractMutator(
): # pylint: disable=too-few-public-methods,too-many-instance-attributes
NAME = ""
HELP = ""
VALID_MUTANTS_COUNT = 0
INVALID_MUTANTS_COUNT = 0
def __init__( # pylint: disable=too-many-arguments
self,
@ -31,7 +29,8 @@ class AbstractMutator(
contract_instance: Contract,
solc_remappings: str | None,
verbose: bool,
output_folder: str,
very_verbose: bool,
output_folder: Path,
dont_mutate_line: List[int],
rate: int = 10,
seed: Optional[int] = None,
@ -45,11 +44,16 @@ class AbstractMutator(
self.timeout = timeout
self.solc_remappings = solc_remappings
self.verbose = verbose
self.very_verbose = very_verbose
self.output_folder = output_folder
self.contract = contract_instance
self.in_file = self.contract.source_mapping.filename.absolute
self.in_file_str = self.contract.compilation_unit.core.source_code[self.in_file]
self.dont_mutate_line = dont_mutate_line
# total revert/comment/tweak mutants that were generated and compiled
self.total_mutant_counts = [0, 0, 0]
# total uncaught revert/comment/tweak mutants
self.uncaught_mutant_counts = [0, 0, 0]
if not self.NAME:
raise IncorrectMutatorInitialization(
@ -71,50 +75,75 @@ class AbstractMutator(
"""TODO Documentation"""
return {}
def mutate(self) -> Tuple[int, int, List[int]]:
# pylint: disable=too-many-branches
def mutate(self) -> Tuple[List[int], List[int], List[int]]:
# call _mutate function from different mutators
(all_patches) = self._mutate()
if "patches" not in all_patches:
logger.debug("No patches found by %s", self.NAME)
return (0, 0, self.dont_mutate_line)
return ([0, 0, 0], [0, 0, 0], self.dont_mutate_line)
for file in all_patches["patches"]:
for file in all_patches["patches"]: # Note: This should only loop over a single file
original_txt = self.slither.source_code[file].encode("utf8")
patches = all_patches["patches"][file]
patches.sort(key=lambda x: x["start"])
logger.info(yellow(f"Mutating {file} with {self.NAME} \n"))
for patch in patches:
# test the patch
flag = test_patch(
patchWasCaught = test_patch(
self.output_folder,
file,
patch,
self.test_command,
self.VALID_MUTANTS_COUNT,
self.NAME,
self.timeout,
self.solc_remappings,
self.verbose,
self.very_verbose,
)
# if RR or CR and valid mutant, add line no.
if self.NAME in ("RR", "CR") and flag:
self.dont_mutate_line.append(patch["line_number"])
# count the valid and invalid mutants
if not flag:
self.INVALID_MUTANTS_COUNT += 1
continue
self.VALID_MUTANTS_COUNT += 1
patched_txt, _ = apply_patch(original_txt, patch, 0)
diff = create_diff(self.compilation_unit, original_txt, patched_txt, file)
if not diff:
logger.info(f"Impossible to generate patch; empty {patches}")
# add valid mutant patches to a output file
with open(
self.output_folder + "/patches_file.txt", "a", encoding="utf8"
) as patches_file:
patches_file.write(diff + "\n")
return (
self.VALID_MUTANTS_COUNT,
self.INVALID_MUTANTS_COUNT,
self.dont_mutate_line,
)
# count the uncaught mutants, flag RR/CR mutants to skip further mutations
if patchWasCaught == 0:
if self.NAME == "RR":
self.uncaught_mutant_counts[0] += 1
self.dont_mutate_line.append(patch["line_number"])
elif self.NAME == "CR":
self.uncaught_mutant_counts[1] += 1
self.dont_mutate_line.append(patch["line_number"])
else:
self.uncaught_mutant_counts[2] += 1
patched_txt, _ = apply_patch(original_txt, patch, 0)
diff = create_diff(self.compilation_unit, original_txt, patched_txt, file)
if not diff:
logger.info(f"Impossible to generate patch; empty {patches}")
# add uncaught mutant patches to a output file
with (self.output_folder / "patches_files.txt").open(
"a", encoding="utf8"
) as patches_file:
patches_file.write(diff + "\n")
# count the total number of mutants that we were able to compile
if patchWasCaught != 2:
if self.NAME == "RR":
self.total_mutant_counts[0] += 1
elif self.NAME == "CR":
self.total_mutant_counts[1] += 1
else:
self.total_mutant_counts[2] += 1
if self.very_verbose:
if self.NAME == "RR":
logger.info(
f"Found {self.uncaught_mutant_counts[0]} uncaught revert mutants so far (out of {self.total_mutant_counts[0]} that compile)"
)
elif self.NAME == "CR":
logger.info(
f"Found {self.uncaught_mutant_counts[1]} uncaught comment mutants so far (out of {self.total_mutant_counts[1]} that compile)"
)
else:
logger.info(
f"Found {self.uncaught_mutant_counts[2]} uncaught tweak mutants so far (out of {self.total_mutant_counts[2]} that compile)"
)
return (self.total_mutant_counts, self.uncaught_mutant_counts, self.dont_mutate_line)

@ -1,106 +1,101 @@
import os
from typing import Dict, List
import traceback
from typing import Dict, List, Union
import logging
from pathlib import Path
import hashlib
logger = logging.getLogger("Slither-Mutate")
duplicated_files = {}
HashedPath = str
backuped_files: Dict[str, HashedPath] = {}
def backup_source_file(source_code: Dict, output_folder: str) -> Dict:
def backup_source_file(source_code: Dict, output_folder: Path) -> Dict[str, HashedPath]:
"""
function to backup the source file
returns: dictionary of duplicated files
"""
os.makedirs(output_folder, exist_ok=True)
output_folder.mkdir(exist_ok=True, parents=True)
for file_path, content in source_code.items():
directory, filename = os.path.split(file_path)
new_filename = f"{output_folder}/backup_{filename}"
new_file_path = os.path.join(directory, new_filename)
path_hash = hashlib.md5(bytes(file_path, "utf8")).hexdigest()
(output_folder / path_hash).write_text(content, encoding="utf8")
with open(new_file_path, "w", encoding="utf8") as new_file:
new_file.write(content)
duplicated_files[file_path] = new_file_path
backuped_files[file_path] = (output_folder / path_hash).as_posix()
return duplicated_files
return backuped_files
def transfer_and_delete(files_dict: Dict) -> None:
def transfer_and_delete(files_dict: Dict[str, HashedPath]) -> None:
"""function to transfer the original content to the sol file after campaign"""
try:
files_dict_copy = files_dict.copy()
for item, value in files_dict_copy.items():
with open(value, "r", encoding="utf8") as duplicated_file:
for original_path, hashed_path in files_dict_copy.items():
with open(hashed_path, "r", encoding="utf8") as duplicated_file:
content = duplicated_file.read()
with open(item, "w", encoding="utf8") as original_file:
with open(original_path, "w", encoding="utf8") as original_file:
original_file.write(content)
os.remove(value)
Path(hashed_path).unlink()
# delete elements from the global dict
del duplicated_files[item]
del backuped_files[original_path]
except Exception as e: # pylint: disable=broad-except
logger.error(f"Error transferring content: {e}")
except FileNotFoundError as e: # pylint: disable=broad-except
logger.error("Error transferring content: %s", e)
global_counter = {}
def create_mutant_file(file: str, count: int, rule: str) -> None:
def create_mutant_file(output_folder: Path, file: str, rule: str) -> None:
"""function to create new mutant file"""
try:
_, filename = os.path.split(file)
if rule not in global_counter:
global_counter[rule] = 0
file_path = Path(file)
# Read content from the duplicated file
with open(file, "r", encoding="utf8") as source_file:
content = source_file.read()
content = file_path.read_text(encoding="utf8")
# Write content to the original file
mutant_name = filename.split(".")[0]
mutant_name = file_path.stem
# create folder for each contract
os.makedirs("mutation_campaign/" + mutant_name, exist_ok=True)
with open(
"mutation_campaign/"
+ mutant_name
+ "/"
+ mutant_name
+ "_"
+ rule
+ "_"
+ str(count)
+ ".sol",
"w",
encoding="utf8",
) as mutant_file:
mutation_dir = output_folder / mutant_name
mutation_dir.mkdir(parents=True, exist_ok=True)
mutation_filename = f"{mutant_name}_{rule}_{global_counter[rule]}.sol"
with (mutation_dir / mutation_filename).open("w", encoding="utf8") as mutant_file:
mutant_file.write(content)
global_counter[rule] += 1
# reset the file
with open(duplicated_files[file], "r", encoding="utf8") as duplicated_file:
duplicate_content = duplicated_file.read()
duplicate_content = Path(backuped_files[file]).read_text("utf8")
with open(file, "w", encoding="utf8") as source_file:
source_file.write(duplicate_content)
except Exception as e: # pylint: disable=broad-except
logger.error(f"Error creating mutant: {e}")
traceback_str = traceback.format_exc()
logger.error(traceback_str) # Log the stack trace
def reset_file(file: str) -> None:
"""function to reset the file"""
try:
# directory, filename = os.path.split(file)
# reset the file
with open(duplicated_files[file], "r", encoding="utf8") as duplicated_file:
with open(backuped_files[file], "r", encoding="utf8") as duplicated_file:
duplicate_content = duplicated_file.read()
with open(file, "w", encoding="utf8") as source_file:
source_file.write(duplicate_content)
except Exception as e: # pylint: disable=broad-except
logger.error(f"Error resetting file: {e}")
logger.error("Error resetting file: %s", e)
def get_sol_file_list(codebase: str, ignore_paths: List[str] | None) -> List[str]:
def get_sol_file_list(codebase: Path, ignore_paths: Union[List[str], None]) -> List[str]:
"""
function to get the contracts list
returns: list of .sol files
@ -110,21 +105,13 @@ def get_sol_file_list(codebase: str, ignore_paths: List[str] | None) -> List[str
ignore_paths = []
# if input is contract file
if os.path.isfile(codebase):
return [codebase]
if codebase.is_file():
return [codebase.as_posix()]
# if input is folder
if os.path.isdir(codebase):
directory = os.path.abspath(codebase)
for file in os.listdir(directory):
filename = os.path.join(directory, file)
if os.path.isfile(filename):
sol_file_list.append(filename)
elif os.path.isdir(filename):
_, dirname = os.path.split(filename)
if dirname in ignore_paths:
continue
for i in get_sol_file_list(filename, ignore_paths):
sol_file_list.append(i)
if codebase.is_dir():
for file_name in codebase.rglob("*.sol"):
if not any(part in ignore_paths for part in file_name.parts):
sol_file_list.append(file_name.as_posix())
return sol_file_list

@ -1,12 +1,11 @@
import subprocess
import os
import logging
import time
import signal
from typing import Dict
import sys
import subprocess
from pathlib import Path
from typing import Dict, Union
import crytic_compile
from slither.tools.mutator.utils.file_handling import create_mutant_file, reset_file
from slither.utils.colors import green, red
from slither.utils.colors import green, red, yellow
logger = logging.getLogger("Slither-Mutate")
@ -23,13 +22,12 @@ def compile_generated_mutant(file_path: str, mappings: str) -> bool:
return False
def run_test_cmd(cmd: str, test_dir: str, timeout: int) -> bool:
def run_test_cmd(cmd: str, timeout: int | None, target_file: str | None, verbose: bool) -> bool:
"""
function to run codebase tests
returns: boolean whether the tests passed or not
"""
# future purpose
_ = test_dir
# add --fail-fast for foundry tests, to exit after first failure
if "forge test" in cmd and "--fail-fast" not in cmd:
cmd += " --fail-fast"
@ -37,41 +35,62 @@ def run_test_cmd(cmd: str, test_dir: str, timeout: int) -> bool:
elif "hardhat test" in cmd or "truffle test" in cmd and "--bail" not in cmd:
cmd += " --bail"
start = time.time()
if timeout is None and "hardhat" not in cmd: # hardhat doesn't support --force flag on tests
# if no timeout, ensure all contracts are recompiled w/out using any cache
cmd += " --force"
try:
result = subprocess.run(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=timeout,
check=False, # True: Raises a CalledProcessError if the return code is non-zero
)
except subprocess.TimeoutExpired:
# Timeout, treat this as a test failure
logger.info("Tests took too long, consider increasing the timeout")
result = None # or set result to a default value
except KeyboardInterrupt:
logger.info(yellow("Ctrl-C received"))
if target_file is not None:
logger.info("Restoring original files")
reset_file(target_file)
logger.info("Exiting")
sys.exit(1)
# if result is 0 then it is an uncaught mutant because tests didn't fail
if result:
code = result.returncode
if code == 0:
return True
# starting new process
with subprocess.Popen([cmd], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as P:
try:
# checking whether the process is completed or not within 30 seconds(default)
while P.poll() is None and (time.time() - start) < timeout:
time.sleep(0.05)
finally:
if P.poll() is None:
logger.error("HAD TO TERMINATE ANALYSIS (TIMEOUT OR EXCEPTION)")
# sends a SIGTERM signal to process group - bascially killing the process
os.killpg(os.getpgid(P.pid), signal.SIGTERM)
# Avoid any weird race conditions from grabbing the return code
time.sleep(0.05)
# indicates whether the command executed sucessfully or not
r = P.returncode
# If tests fail in verbose-mode, print both stdout and stderr for easier debugging
if verbose:
logger.info(yellow(result.stdout.decode("utf-8")))
logger.info(red(result.stderr.decode("utf-8")))
# if r is 0 then it is valid mutant because tests didn't fail
return r == 0
return False
# return 0 if uncaught, 1 if caught, and 2 if compilation fails
def test_patch( # pylint: disable=too-many-arguments
output_folder: Path,
file: str,
patch: Dict,
command: str,
index: int,
generator_name: str,
timeout: int,
mappings: str | None,
mappings: Union[str, None],
verbose: bool,
) -> bool:
very_verbose: bool,
) -> int:
"""
function to verify the validity of each patch
returns: valid or invalid patch
function to verify whether each patch is caught by tests
returns: 0 (uncaught), 1 (caught), or 2 (compilation failure)
"""
with open(file, "r", encoding="utf-8") as filepath:
content = filepath.read()
@ -80,21 +99,36 @@ def test_patch( # pylint: disable=too-many-arguments
# Write the modified content back to the file
with open(file, "w", encoding="utf-8") as filepath:
filepath.write(replaced_content)
if compile_generated_mutant(file, mappings):
if run_test_cmd(command, file, timeout):
create_mutant_file(file, index, generator_name)
print(
green(
f"String '{patch['old_string']}' replaced with '{patch['new_string']}' at line no. '{patch['line_number']}' ---> VALID\n"
if run_test_cmd(command, timeout, file, False):
create_mutant_file(output_folder, file, generator_name)
logger.info(
red(
f"[{generator_name}] Line {patch['line_number']}: '{patch['old_string']}' ==> '{patch['new_string']}' --> UNCAUGHT"
)
)
return True
reset_file(file)
return 0 # uncaught
else:
if very_verbose:
logger.info(
yellow(
f"[{generator_name}] Line {patch['line_number']}: '{patch['old_string']}' ==> '{patch['new_string']}' --> COMPILATION FAILURE"
)
)
reset_file(file)
return 2 # compile failure
reset_file(file)
if verbose:
print(
red(
f"String '{patch['old_string']}' replaced with '{patch['new_string']}' at line no. '{patch['line_number']}' ---> INVALID\n"
logger.info(
green(
f"[{generator_name}] Line {patch['line_number']}: '{patch['old_string']}' ==> '{patch['new_string']}' --> CAUGHT"
)
)
return False
reset_file(file)
return 1 # caught

File diff suppressed because it is too large Load Diff

@ -4,10 +4,10 @@ import logging
import os
import zipfile
from collections import OrderedDict
from importlib import metadata
from typing import Tuple, Optional, Dict, List, Union, Any, TYPE_CHECKING, Type
from zipfile import ZipFile
from pkg_resources import require
from slither.core.cfg.node import Node
from slither.core.declarations import (
@ -161,7 +161,7 @@ def output_to_sarif(
"driver": {
"name": "Slither",
"informationUri": "https://github.com/crytic/slither",
"version": require("slither-analyzer")[0].version,
"version": metadata.version("slither-analyzer"),
"rules": [],
}
},

@ -1,7 +1,17 @@
from typing import List
from typing import List, Set
from crytic_compile import CryticCompile
from slither.core.declarations import Contract, Function, Enum, Event, Import, Pragma, Structure
from slither.core.solidity_types.type import Type
from slither.core.declarations import (
Contract,
Function,
Enum,
Event,
Import,
Pragma,
Structure,
CustomError,
FunctionContract,
)
from slither.core.solidity_types import Type, TypeAlias
from slither.core.source_mapping.source_mapping import Source, SourceMapping
from slither.core.variables.variable import Variable
from slither.exceptions import SlitherError
@ -15,6 +25,10 @@ def get_definition(target: SourceMapping, crytic_compile: CryticCompile) -> Sour
pattern = "import"
elif isinstance(target, Pragma):
pattern = "pragma" # todo maybe return with the while pragma statement
elif isinstance(target, CustomError):
pattern = "error"
elif isinstance(target, TypeAlias):
pattern = "type"
elif isinstance(target, Type):
raise SlitherError("get_definition_generic not implemented for types")
else:
@ -52,5 +66,34 @@ def get_implementation(target: SourceMapping) -> Source:
return target.source_mapping
def get_all_implementations(target: SourceMapping, contracts: List[Contract]) -> Set[Source]:
"""
Get all implementations of a contract or function, accounting for inheritance and overrides
"""
implementations = set()
# Abstract contracts and interfaces are implemented by their children
if isinstance(target, Contract):
is_interface = target.is_interface
is_implicitly_abstract = not target.is_fully_implemented
is_explicitly_abstract = target.is_abstract
if is_interface or is_implicitly_abstract or is_explicitly_abstract:
for contract in contracts:
if target in contract.immediate_inheritance:
implementations.add(contract.source_mapping)
# Parent's virtual functions may be overridden by children
elif isinstance(target, FunctionContract):
for over in target.overridden_by:
implementations.add(over.source_mapping)
# Only show implemented virtual functions
if not target.is_virtual or target.is_implemented:
implementations.add(get_implementation(target))
else:
implementations.add(get_implementation(target))
return implementations
def get_references(target: SourceMapping) -> List[Source]:
return target.references

@ -60,7 +60,6 @@ def compare(
List[Function],
List[Function],
List[Function],
List[TaintedExternalContract],
]:
"""
Compares two versions of a contract. Most useful for upgradeable (logic) contracts,
@ -392,7 +391,7 @@ def get_proxy_implementation_var(proxy: Contract) -> Optional[Variable]:
try:
delegate = next(var for var in dependencies if isinstance(var, StateVariable))
except StopIteration:
# TODO: Handle cases where get_dependencies doesn't return any state variables.
# TODO: Handle case where get_dependencies does not return any state variables.
return delegate
return delegate

@ -0,0 +1,17 @@
from typing import Dict, List, TYPE_CHECKING, Union
from slither.core.solidity_types import Type, UserDefinedType
if TYPE_CHECKING:
from slither.core.declarations import Function
USING_FOR_KEY = Union[str, Type] # "*" is wildcard
USING_FOR_ITEM = List[Union[UserDefinedType, "Function"]] # UserDefinedType.type is a library
USING_FOR = Dict[USING_FOR_KEY, USING_FOR_ITEM]
def merge_using_for(uf1: USING_FOR, uf2: USING_FOR) -> USING_FOR:
result = {**uf1, **uf2}
for key, value in result.items():
if key in uf1 and key in uf2:
result[key] = value + uf1[key]
return result

@ -210,12 +210,6 @@ class ExpressionToSlithIR(ExpressionVisitor):
for idx, _ in enumerate(left):
if not left[idx] is None:
index = idx
# The following test is probably always true?
if (
isinstance(left[idx], LocalVariableInitFromTuple)
and left[idx].tuple_index is not None
):
index = left[idx].tuple_index
operation = Unpack(left[idx], right, index)
operation.set_expression(expression)
self._result.append(operation)
@ -233,15 +227,16 @@ class ExpressionToSlithIR(ExpressionVisitor):
self._result.append(operation)
set_val(expression, None)
else:
# Init of array, like
# uint8[2] var = [1,2];
# For `InitArray`, the rhs is a list or singleton of `TupleExpression` elements.
# Init of array e.g. uint8[2] var = [1,2];
if isinstance(right, list):
operation = InitArray(right, left)
operation.set_expression(expression)
self._result.append(operation)
set_val(expression, left)
elif isinstance(left.type, ArrayType):
# Special case for init of array, when the right has only one element
# Special case for init of array, when the right has only one element e.g. arr = [1];
elif isinstance(left.type, ArrayType) and not isinstance(right.type, ArrayType):
operation = InitArray([right], left)
operation.set_expression(expression)
self._result.append(operation)
@ -276,6 +271,7 @@ class ExpressionToSlithIR(ExpressionVisitor):
self._result.append(operation)
else:
operation = convert_assignment(
left, right, expression.type, expression.expression_return_type
)
@ -436,7 +432,7 @@ class ExpressionToSlithIR(ExpressionVisitor):
set_val(expression, val)
def _post_conditional_expression(self, expression: ConditionalExpression) -> None:
raise Exception(f"Ternary operator are not convertible to SlithIR {expression}")
raise SlithIRError(f"Ternary operator are not convertible to SlithIR {expression}")
def _post_elementary_type_name_expression(
self,
@ -591,8 +587,8 @@ class ExpressionToSlithIR(ExpressionVisitor):
# contract A { type MyInt is int}
# contract B { function f() public{ A.MyInt test = A.MyInt.wrap(1);}}
# The logic is handled by _post_call_expression
if expression.member_name in expr.file_scope.type_aliases:
set_val(expression, expr.file_scope.type_aliases[expression.member_name])
if expression.member_name in expr.type_aliases_as_dict:
set_val(expression, expr.type_aliases_as_dict[expression.member_name])
return
# Lookup errors referred to as member of contract e.g. Test.myError.selector
if expression.member_name in expr.custom_errors_as_dict:

@ -153,10 +153,10 @@ def parse_expression(
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
# `raw_call` and `send` are treated specially in order to force `extract_tmp_call` to treat this as a `HighLevelCall` which will be converted
# to a `LowLevelCall` by `convert_to_low_level`. This is an artifact of the late conversion of Solidity...
if called.value.name == "raw_call()":
args = [parse_expression(a, caller_context) for a in expression.args]
# This is treated specially in order to force `extract_tmp_call` to treat this as a `HighLevelCall` which will be converted
# to a `LowLevelCall` by `convert_to_low_level`. This is an artifact of the late conversion of Solidity...
call = CallExpression(
MemberAccess("raw_call", "tuple(bool,bytes32)", args[0]),
args[1:],
@ -183,6 +183,17 @@ def parse_expression(
return call
if called.value.name == "send()":
args = [parse_expression(a, caller_context) for a in expression.args]
call = CallExpression(
MemberAccess("send", "tuple()", args[0]),
args[1:],
"tuple()",
)
call.set_offset(expression.src, caller_context.compilation_unit)
return call
if expression.args and isinstance(expression.args[0], VyDict):
arguments = []
for val in expression.args[0].values:

@ -61,7 +61,7 @@ def solc_binary_path(shared_directory):
@pytest.fixture
def slither_from_solidity_source(solc_binary_path):
@contextmanager
def inner(source_code: str, solc_version: str = "0.8.19"):
def inner(source_code: str, solc_version: str = "0.8.19", legacy: bool = False):
"""Yields a Slither instance using source_code string and solc_version.
Creates a temporary file and compiles with solc_version.
"""
@ -72,7 +72,7 @@ def slither_from_solidity_source(solc_binary_path):
fname = f.name
f.write(source_code)
solc_path = solc_binary_path(solc_version)
yield Slither(fname, solc=solc_path)
yield Slither(fname, solc=solc_path, force_legacy=legacy)
finally:
Path(fname).unlink()

@ -1,5 +1,6 @@
Different versions of Solidity are used:
- Version used: ['^0.4.24', '^0.4.25']
- ^0.4.24 (tests/e2e/detectors/test_data/pragma/0.4.25/pragma.0.4.24.sol#1)
- ^0.4.25 (tests/e2e/detectors/test_data/pragma/0.4.25/pragma.0.4.25.sol#1)
2 different versions of Solidity are used:
- Version constraint ^0.4.25 is used by:
- tests/e2e/detectors/test_data/pragma/0.4.25/pragma.0.4.25.sol#1
- Version constraint ^0.4.24 is used by:
- tests/e2e/detectors/test_data/pragma/0.4.25/pragma.0.4.24.sol#1

@ -1,5 +1,6 @@
Different versions of Solidity are used:
- Version used: ['^0.5.15', '^0.5.16']
- ^0.5.15 (tests/e2e/detectors/test_data/pragma/0.5.16/pragma.0.5.15.sol#1)
- ^0.5.16 (tests/e2e/detectors/test_data/pragma/0.5.16/pragma.0.5.16.sol#1)
2 different versions of Solidity are used:
- Version constraint ^0.5.16 is used by:
- tests/e2e/detectors/test_data/pragma/0.5.16/pragma.0.5.16.sol#1
- Version constraint ^0.5.15 is used by:
- tests/e2e/detectors/test_data/pragma/0.5.16/pragma.0.5.15.sol#1

@ -1,5 +1,6 @@
Different versions of Solidity are used:
- Version used: ['^0.6.10', '^0.6.11']
- ^0.6.10 (tests/e2e/detectors/test_data/pragma/0.6.11/pragma.0.6.10.sol#1)
- ^0.6.11 (tests/e2e/detectors/test_data/pragma/0.6.11/pragma.0.6.11.sol#1)
2 different versions of Solidity are used:
- Version constraint ^0.6.11 is used by:
- tests/e2e/detectors/test_data/pragma/0.6.11/pragma.0.6.11.sol#1
- Version constraint ^0.6.10 is used by:
- tests/e2e/detectors/test_data/pragma/0.6.11/pragma.0.6.10.sol#1

@ -1,5 +1,6 @@
Different versions of Solidity are used:
- Version used: ['^0.7.5', '^0.7.6']
- ^0.7.5 (tests/e2e/detectors/test_data/pragma/0.7.6/pragma.0.7.5.sol#1)
- ^0.7.6 (tests/e2e/detectors/test_data/pragma/0.7.6/pragma.0.7.6.sol#1)
2 different versions of Solidity are used:
- Version constraint ^0.7.6 is used by:
- tests/e2e/detectors/test_data/pragma/0.7.6/pragma.0.7.6.sol#1
- Version constraint ^0.7.5 is used by:
- tests/e2e/detectors/test_data/pragma/0.7.6/pragma.0.7.5.sol#1

@ -1,4 +1,21 @@
solc-0.4.25 is not recommended for deployment
solc-0.4.25 is an outdated solc version. Use a more recent version (at least 0.8.0), if possible.
Pragma version0.4.25 (tests/e2e/detectors/test_data/solc-version/0.4.25/static.sol#1) allows old versions
Version constraint 0.4.25 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)
- DirtyBytesArrayToStorage
- ABIDecodeTwoDimensionalArrayMemory
- KeccakCaching
- EmptyByteArrayCopy
- DynamicArrayCleanup
- ImplicitConstructorCallvalueCheck
- TupleAssignmentMultiStackSlotComponents
- MemoryArrayCreationOverflow
- privateCanBeOverridden
- SignedArrayStorageCopy
- ABIEncoderV2StorageArrayWithMultiSlotElement
- DynamicConstructorArgumentsClippedABIV2
- UninitializedFunctionPointerInConstructor_0.4.x
- IncorrectEventSignatureInLibraries_0.4.x
- ABIEncoderV2PackedStorage_0.4.x.
It is used by:
- tests/e2e/detectors/test_data/solc-version/0.4.25/static.sol#1

@ -1,3 +1,20 @@
Pragma version0.5.14 (tests/e2e/detectors/test_data/solc-version/0.5.14/static.sol#1) is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)
Version constraint 0.5.14 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)
- AbiReencodingHeadOverflowWithStaticArrayCleanup
- DirtyBytesArrayToStorage
- NestedCalldataArrayAbiReencodingSizeValidation
- ABIDecodeTwoDimensionalArrayMemory
- KeccakCaching
- EmptyByteArrayCopy
- DynamicArrayCleanup
- MissingEscapingInFormatting
- ImplicitConstructorCallvalueCheck
- TupleAssignmentMultiStackSlotComponents
- MemoryArrayCreationOverflow
- privateCanBeOverridden
- YulOptimizerRedundantAssignmentBreakContinue0.5
- ABIEncoderV2LoopYulOptimizer.
It is used by:
- tests/e2e/detectors/test_data/solc-version/0.5.14/static.sol#1
solc-0.5.14 is an outdated solc version. Use a more recent version (at least 0.8.0), if possible.
solc-0.5.14 is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)

@ -1,4 +1,19 @@
Pragma version^0.5.15 (tests/e2e/detectors/test_data/solc-version/0.5.16/dynamic_1.sol#1) allows old versions
Version constraint ^0.5.15 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)
- AbiReencodingHeadOverflowWithStaticArrayCleanup
- DirtyBytesArrayToStorage
- NestedCalldataArrayAbiReencodingSizeValidation
- ABIDecodeTwoDimensionalArrayMemory
- KeccakCaching
- EmptyByteArrayCopy
- DynamicArrayCleanup
- MissingEscapingInFormatting
- ImplicitConstructorCallvalueCheck
- TupleAssignmentMultiStackSlotComponents
- MemoryArrayCreationOverflow
- privateCanBeOverridden
- YulOptimizerRedundantAssignmentBreakContinue0.5.
It is used by:
- tests/e2e/detectors/test_data/solc-version/0.5.16/dynamic_1.sol#1
solc-0.5.16 is not recommended for deployment
solc-0.5.16 is an outdated solc version. Use a more recent version (at least 0.8.0), if possible.

@ -1,4 +1,21 @@
Pragma version>=0.5.0<0.6.0 (tests/e2e/detectors/test_data/solc-version/0.5.16/dynamic_2.sol#1) allows old versions
Version constraint >=0.5.0<0.6.0 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)
- DirtyBytesArrayToStorage
- ABIDecodeTwoDimensionalArrayMemory
- KeccakCaching
- EmptyByteArrayCopy
- DynamicArrayCleanup
- ImplicitConstructorCallvalueCheck
- TupleAssignmentMultiStackSlotComponents
- MemoryArrayCreationOverflow
- privateCanBeOverridden
- SignedArrayStorageCopy
- ABIEncoderV2StorageArrayWithMultiSlotElement
- DynamicConstructorArgumentsClippedABIV2
- UninitializedFunctionPointerInConstructor
- IncorrectEventSignatureInLibraries
- ABIEncoderV2PackedStorage.
It is used by:
- tests/e2e/detectors/test_data/solc-version/0.5.16/dynamic_2.sol#1
solc-0.5.16 is not recommended for deployment
solc-0.5.16 is an outdated solc version. Use a more recent version (at least 0.8.0), if possible.

@ -1,4 +1,18 @@
solc-0.5.16 is not recommended for deployment
Version constraint 0.5.16 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)
- AbiReencodingHeadOverflowWithStaticArrayCleanup
- DirtyBytesArrayToStorage
- NestedCalldataArrayAbiReencodingSizeValidation
- ABIDecodeTwoDimensionalArrayMemory
- KeccakCaching
- EmptyByteArrayCopy
- DynamicArrayCleanup
- MissingEscapingInFormatting
- ImplicitConstructorCallvalueCheck
- TupleAssignmentMultiStackSlotComponents
- MemoryArrayCreationOverflow
- privateCanBeOverridden.
It is used by:
- tests/e2e/detectors/test_data/solc-version/0.5.16/static.sol#1
Pragma version0.5.16 (tests/e2e/detectors/test_data/solc-version/0.5.16/static.sol#1) allows old versions
solc-0.5.16 is an outdated solc version. Use a more recent version (at least 0.8.0), if possible.

@ -1,4 +1,17 @@
solc-0.6.10 is not recommended for deployment
solc-0.6.10 is an outdated solc version. Use a more recent version (at least 0.8.0), if possible.
Pragma version0.6.10 (tests/e2e/detectors/test_data/solc-version/0.6.10/static.sol#1) allows old versions
Version constraint 0.6.10 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)
- FullInlinerNonExpressionSplitArgumentEvaluationOrder
- MissingSideEffectsOnSelectorAccess
- AbiReencodingHeadOverflowWithStaticArrayCleanup
- DirtyBytesArrayToStorage
- DataLocationChangeInInternalOverride
- NestedCalldataArrayAbiReencodingSizeValidation
- SignedImmutables
- ABIDecodeTwoDimensionalArrayMemory
- KeccakCaching
- EmptyByteArrayCopy
- DynamicArrayCleanup.
It is used by:
- tests/e2e/detectors/test_data/solc-version/0.6.10/static.sol#1

@ -1,4 +1,17 @@
solc-0.6.11 is not recommended for deployment
Version constraint ^0.6.10 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)
- FullInlinerNonExpressionSplitArgumentEvaluationOrder
- MissingSideEffectsOnSelectorAccess
- AbiReencodingHeadOverflowWithStaticArrayCleanup
- DirtyBytesArrayToStorage
- DataLocationChangeInInternalOverride
- NestedCalldataArrayAbiReencodingSizeValidation
- SignedImmutables
- ABIDecodeTwoDimensionalArrayMemory
- KeccakCaching
- EmptyByteArrayCopy
- DynamicArrayCleanup.
It is used by:
- tests/e2e/detectors/test_data/solc-version/0.6.11/dynamic_1.sol#1
Pragma version^0.6.10 (tests/e2e/detectors/test_data/solc-version/0.6.11/dynamic_1.sol#1) allows old versions
solc-0.6.11 is an outdated solc version. Use a more recent version (at least 0.8.0), if possible.

@ -1,4 +1,19 @@
Pragma version>=0.6.0<0.7.0 (tests/e2e/detectors/test_data/solc-version/0.6.11/dynamic_2.sol#1) allows old versions
Version constraint >=0.6.0<0.7.0 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)
- AbiReencodingHeadOverflowWithStaticArrayCleanup
- DirtyBytesArrayToStorage
- NestedCalldataArrayAbiReencodingSizeValidation
- ABIDecodeTwoDimensionalArrayMemory
- KeccakCaching
- EmptyByteArrayCopy
- DynamicArrayCleanup
- MissingEscapingInFormatting
- ArraySliceDynamicallyEncodedBaseType
- ImplicitConstructorCallvalueCheck
- TupleAssignmentMultiStackSlotComponents
- MemoryArrayCreationOverflow
- YulOptimizerRedundantAssignmentBreakContinue.
It is used by:
- tests/e2e/detectors/test_data/solc-version/0.6.11/dynamic_2.sol#1
solc-0.6.11 is not recommended for deployment
solc-0.6.11 is an outdated solc version. Use a more recent version (at least 0.8.0), if possible.

@ -1,4 +1,17 @@
Pragma version0.6.11 (tests/e2e/detectors/test_data/solc-version/0.6.11/static.sol#1) allows old versions
Version constraint 0.6.11 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)
- FullInlinerNonExpressionSplitArgumentEvaluationOrder
- MissingSideEffectsOnSelectorAccess
- AbiReencodingHeadOverflowWithStaticArrayCleanup
- DirtyBytesArrayToStorage
- DataLocationChangeInInternalOverride
- NestedCalldataArrayAbiReencodingSizeValidation
- SignedImmutables
- ABIDecodeTwoDimensionalArrayMemory
- KeccakCaching
- EmptyByteArrayCopy
- DynamicArrayCleanup.
It is used by:
- tests/e2e/detectors/test_data/solc-version/0.6.11/static.sol#1
solc-0.6.11 is not recommended for deployment
solc-0.6.11 is an outdated solc version. Use a more recent version (at least 0.8.0), if possible.

@ -1,4 +1,15 @@
Pragma version0.7.4 (tests/e2e/detectors/test_data/solc-version/0.7.4/static.sol#1) allows old versions
Version constraint 0.7.4 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)
- FullInlinerNonExpressionSplitArgumentEvaluationOrder
- MissingSideEffectsOnSelectorAccess
- AbiReencodingHeadOverflowWithStaticArrayCleanup
- DirtyBytesArrayToStorage
- DataLocationChangeInInternalOverride
- NestedCalldataArrayAbiReencodingSizeValidation
- SignedImmutables
- ABIDecodeTwoDimensionalArrayMemory
- KeccakCaching.
It is used by:
- tests/e2e/detectors/test_data/solc-version/0.7.4/static.sol#1
solc-0.7.4 is not recommended for deployment
solc-0.7.4 is an outdated solc version. Use a more recent version (at least 0.8.0), if possible.

@ -1,4 +1,15 @@
Pragma version^0.7.4 (tests/e2e/detectors/test_data/solc-version/0.7.6/dynamic_1.sol#1) allows old versions
solc-0.7.6 is an outdated solc version. Use a more recent version (at least 0.8.0), if possible.
solc-0.7.6 is not recommended for deployment
Version constraint ^0.7.4 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)
- FullInlinerNonExpressionSplitArgumentEvaluationOrder
- MissingSideEffectsOnSelectorAccess
- AbiReencodingHeadOverflowWithStaticArrayCleanup
- DirtyBytesArrayToStorage
- DataLocationChangeInInternalOverride
- NestedCalldataArrayAbiReencodingSizeValidation
- SignedImmutables
- ABIDecodeTwoDimensionalArrayMemory
- KeccakCaching.
It is used by:
- tests/e2e/detectors/test_data/solc-version/0.7.6/dynamic_1.sol#1

@ -1,4 +1,6 @@
Pragma version>=0.7.0<=0.7.6 (tests/e2e/detectors/test_data/solc-version/0.7.6/dynamic_2.sol#1) is too complex
Version constraint >=0.7.0<=0.7.6 is too complex.
It is used by:
- tests/e2e/detectors/test_data/solc-version/0.7.6/dynamic_2.sol#1
solc-0.7.6 is not recommended for deployment
solc-0.7.6 is an outdated solc version. Use a more recent version (at least 0.8.0), if possible.

@ -1,4 +1,15 @@
Pragma version0.7.6 (tests/e2e/detectors/test_data/solc-version/0.7.6/static.sol#1) allows old versions
solc-0.7.6 is an outdated solc version. Use a more recent version (at least 0.8.0), if possible.
solc-0.7.6 is not recommended for deployment
Version constraint 0.7.6 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)
- FullInlinerNonExpressionSplitArgumentEvaluationOrder
- MissingSideEffectsOnSelectorAccess
- AbiReencodingHeadOverflowWithStaticArrayCleanup
- DirtyBytesArrayToStorage
- DataLocationChangeInInternalOverride
- NestedCalldataArrayAbiReencodingSizeValidation
- SignedImmutables
- ABIDecodeTwoDimensionalArrayMemory
- KeccakCaching.
It is used by:
- tests/e2e/detectors/test_data/solc-version/0.7.6/static.sol#1

@ -1,3 +1,6 @@
Modifier Test.loopsNoResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.4.25/modifier_default.sol#30-41) does not always execute _; or revert
Modifier Test.requireAssertNoResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.4.25/modifier_default.sol#18-22) does not always execute _; or revert
Modifier Test.noResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.4.25/modifier_default.sol#2-6) does not always execute _; or revert

@ -1,3 +1,6 @@
Modifier Test.loopsNoResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.5.16/modifier_default.sol#30-41) does not always execute _; or revert
Modifier Test.requireAssertNoResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.5.16/modifier_default.sol#18-22) does not always execute _; or revert
Modifier Test.noResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.5.16/modifier_default.sol#2-6) does not always execute _; or revert

@ -1,3 +1,6 @@
Modifier Test.loopsNoResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.6.11/modifier_default.sol#30-41) does not always execute _; or revert
Modifier Test.requireAssertNoResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.6.11/modifier_default.sol#18-22) does not always execute _; or revert
Modifier Test.noResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.6.11/modifier_default.sol#2-6) does not always execute _; or revert

@ -1,3 +1,6 @@
Modifier Test.loopsNoResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.7.6/modifier_default.sol#30-41) does not always execute _; or revert
Modifier Test.requireAssertNoResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.7.6/modifier_default.sol#18-22) does not always execute _; or revert
Modifier Test.noResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.7.6/modifier_default.sol#2-6) does not always execute _; or revert

@ -1,3 +1,9 @@
Reentrancy in ReentrancyBenign.bad0() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#16-22):
External calls:
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#17)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#21)
Reentrancy in ReentrancyBenign.bad3(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#41-45):
External calls:
- externalCaller(target) (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#42)
@ -6,11 +12,11 @@ Reentrancy in ReentrancyBenign.bad3(address) (tests/e2e/detectors/test_data/reen
- varChanger() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#43)
- anotherVariableToChange ++ (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#69)
Reentrancy in ReentrancyBenign.bad0() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#16-22):
Reentrancy in ReentrancyBenign.bad1(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#24-28):
External calls:
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#17)
- (success,None) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#25)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#21)
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#27)
Reentrancy in ReentrancyBenign.bad5(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#54-58):
External calls:
@ -33,15 +39,9 @@ Reentrancy in ReentrancyBenign.bad4(address) (tests/e2e/detectors/test_data/reen
- varChanger() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#50)
- anotherVariableToChange ++ (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#69)
Reentrancy in ReentrancyBenign.bad1(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#24-28):
External calls:
- (success) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#25)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#27)
Reentrancy in ReentrancyBenign.bad2(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#30-39):
External calls:
- (success) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#31)
- (success,None) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#31)
- address(target).call.value(1000)() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#33)
External calls sending eth:
- address(target).call.value(1000)() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#33)

@ -1,3 +1,12 @@
Reentrancy in ReentrancyBenign.bad2(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#30-39):
External calls:
- (success,None) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#31)
- address(target).call.value(1000)() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#33)
External calls sending eth:
- address(target).call.value(1000)() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#33)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#34)
Reentrancy in ReentrancyBenign.bad4(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#47-52):
External calls:
- externalCaller(target) (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#48)
@ -19,21 +28,6 @@ Reentrancy in ReentrancyBenign.bad3(address) (tests/e2e/detectors/test_data/reen
- varChanger() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#43)
- anotherVariableToChange ++ (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#69)
Reentrancy in ReentrancyBenign.bad2(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#30-39):
External calls:
- (success) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#31)
- address(target).call.value(1000)() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#33)
External calls sending eth:
- address(target).call.value(1000)() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#33)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#34)
Reentrancy in ReentrancyBenign.bad1(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#24-28):
External calls:
- (success) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#25)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#27)
Reentrancy in ReentrancyBenign.bad5(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#54-58):
External calls:
- ethSender(address(0)) (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#55)
@ -44,7 +38,13 @@ Reentrancy in ReentrancyBenign.bad5(address) (tests/e2e/detectors/test_data/reen
Reentrancy in ReentrancyBenign.bad0() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#16-22):
External calls:
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#17)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#17)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#21)
Reentrancy in ReentrancyBenign.bad1(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#24-28):
External calls:
- (success,None) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#25)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#27)

@ -1,20 +1,26 @@
Reentrancy in ReentrancyBenign.bad3(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#41-45):
Reentrancy in ReentrancyBenign.bad0() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#16-22):
External calls:
- externalCaller(target) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#42)
- address(target).call() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#61)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#17)
State variables written after the call(s):
- varChanger() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#43)
- anotherVariableToChange ++ (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#69)
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#21)
Reentrancy in ReentrancyBenign.bad2(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#30-39):
External calls:
- (success) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#31)
- (success,None) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#31)
- address(target).call{value: 1000}() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#33)
External calls sending eth:
- address(target).call{value: 1000}() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#33)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#34)
Reentrancy in ReentrancyBenign.bad3(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#41-45):
External calls:
- externalCaller(target) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#42)
- address(target).call() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#61)
State variables written after the call(s):
- varChanger() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#43)
- anotherVariableToChange ++ (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#69)
Reentrancy in ReentrancyBenign.bad5(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#54-58):
External calls:
- ethSender(address(0)) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#55)
@ -23,6 +29,12 @@ Reentrancy in ReentrancyBenign.bad5(address) (tests/e2e/detectors/test_data/reen
- varChanger() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#56)
- anotherVariableToChange ++ (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#69)
Reentrancy in ReentrancyBenign.bad1(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#24-28):
External calls:
- (success,None) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#25)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#27)
Reentrancy in ReentrancyBenign.bad4(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#47-52):
External calls:
- externalCaller(target) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#48)
@ -36,15 +48,3 @@ Reentrancy in ReentrancyBenign.bad4(address) (tests/e2e/detectors/test_data/reen
- varChanger() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#50)
- anotherVariableToChange ++ (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#69)
Reentrancy in ReentrancyBenign.bad1(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#24-28):
External calls:
- (success) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#25)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#27)
Reentrancy in ReentrancyBenign.bad0() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#16-22):
External calls:
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#17)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#21)

@ -1,6 +1,6 @@
Reentrancy in ReentrancyWrite.bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#16-23):
External calls:
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#18)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#18)
State variables written after the call(s):
- notCalled = false (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#22)
ReentrancyWrite.notCalled (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#4) can be used in cross function reentrancies:
@ -11,9 +11,9 @@ Reentrancy in ReentrancyWrite.bad0() (tests/e2e/detectors/test_data/reentrancy-n
Reentrancy in ReentrancyWrite.bad1(address) (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#25-30):
External calls:
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#27)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#27)
- bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#29)
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#18)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#18)
State variables written after the call(s):
- bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#29)
- notCalled = false (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#22)

@ -1,22 +1,22 @@
Reentrancy in ReentrancyWrite.bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#16-23):
Reentrancy in ReentrancyWrite.bad1(address) (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#25-30):
External calls:
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#18)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#27)
- bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#29)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#18)
State variables written after the call(s):
- notCalled = false (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#22)
- bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#29)
- notCalled = false (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#22)
ReentrancyWrite.notCalled (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#4) can be used in cross function reentrancies:
- ReentrancyWrite.bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#16-23)
- ReentrancyWrite.bad1(address) (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#25-30)
- ReentrancyWrite.constructor(address) (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#7-14)
- ReentrancyWrite.good() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#32-39)
Reentrancy in ReentrancyWrite.bad1(address) (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#25-30):
Reentrancy in ReentrancyWrite.bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#16-23):
External calls:
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#27)
- bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#29)
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#18)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#18)
State variables written after the call(s):
- bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#29)
- notCalled = false (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#22)
- notCalled = false (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#22)
ReentrancyWrite.notCalled (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#4) can be used in cross function reentrancies:
- ReentrancyWrite.bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#16-23)
- ReentrancyWrite.bad1(address) (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#25-30)

@ -1,6 +1,6 @@
Reentrancy in ReentrancyWrite.bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#20-27):
External calls:
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#22)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#22)
State variables written after the call(s):
- notCalled = false (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#26)
ReentrancyWrite.notCalled (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#8) can be used in cross function reentrancies:
@ -11,9 +11,9 @@ Reentrancy in ReentrancyWrite.bad0() (tests/e2e/detectors/test_data/reentrancy-n
Reentrancy in ReentrancyWrite.bad1(address) (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#29-34):
External calls:
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#31)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#31)
- bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#33)
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#22)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#22)
State variables written after the call(s):
- bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#33)
- notCalled = false (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#26)

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

Loading…
Cancel
Save