Merge branch 'dev' into add-generic-taint

pull/1769/head
alpharush 2 years ago
commit 90a5977101
  1. 38
      .github/DISCUSSION_TEMPLATE/installation.yml
  2. 55
      .github/DISCUSSION_TEMPLATE/trouble_with_installation.yml
  3. 2
      .github/ISSUE_TEMPLATE/false_negative.yml
  4. 30
      .github/actions/upload-coverage/action.yml
  5. 13
      .github/scripts/integration_test_runner.sh
  6. 13
      .github/scripts/tool_test_runner.sh
  7. 13
      .github/scripts/unit_test_runner.sh
  8. 51
      .github/workflows/IR.yml
  9. 2
      .github/workflows/black.yml
  10. 4
      .github/workflows/ci.yml
  11. 45
      .github/workflows/detectors.yml
  12. 8
      .github/workflows/docs.yml
  13. 56
      .github/workflows/features.yml
  14. 4
      .github/workflows/linter.yml
  15. 49
      .github/workflows/parser.yml
  16. 2
      .github/workflows/pip-audit.yml
  17. 2
      .github/workflows/pylint.yml
  18. 53
      .github/workflows/read_storage.yml
  19. 105
      .github/workflows/test.yml
  20. 1
      .gitignore
  21. 83
      CONTRIBUTING.md
  22. 88
      Makefile
  23. 43
      README.md
  24. 6
      examples/scripts/data_dependency.py
  25. 1
      examples/scripts/variable_in_condition.py
  26. 6
      scripts/ci_test_cli.sh
  27. 2
      scripts/ci_test_erc.sh
  28. 2
      scripts/ci_test_path_filtering.sh
  29. 2
      scripts/ci_test_printers.sh
  30. 23
      setup.py
  31. 3
      slither/__init__.py
  32. 67
      slither/__main__.py
  33. 146
      slither/analyses/data_dependency/data_dependency.py
  34. 29
      slither/analyses/write/are_variables_written.py
  35. 115
      slither/core/cfg/node.py
  36. 19
      slither/core/children/child_contract.py
  37. 17
      slither/core/children/child_event.py
  38. 18
      slither/core/children/child_expression.py
  39. 17
      slither/core/children/child_function.py
  40. 17
      slither/core/children/child_inheritance.py
  41. 31
      slither/core/children/child_node.py
  42. 17
      slither/core/children/child_structure.py
  43. 19
      slither/core/compilation_unit.py
  44. 108
      slither/core/declarations/contract.py
  45. 29
      slither/core/declarations/contract_level.py
  46. 10
      slither/core/declarations/custom_error.py
  47. 12
      slither/core/declarations/custom_error_contract.py
  48. 2
      slither/core/declarations/custom_error_top_level.py
  49. 4
      slither/core/declarations/enum_contract.py
  50. 4
      slither/core/declarations/event.py
  51. 7
      slither/core/declarations/function.py
  52. 29
      slither/core/declarations/function_contract.py
  53. 12
      slither/core/declarations/solidity_variables.py
  54. 4
      slither/core/declarations/structure_contract.py
  55. 6
      slither/core/declarations/top_level.py
  56. 3
      slither/core/declarations/using_for_top_level.py
  57. 1
      slither/core/dominators/utils.py
  58. 5
      slither/core/expressions/assignment_operation.py
  59. 3
      slither/core/expressions/binary_operation.py
  60. 8
      slither/core/expressions/call_expression.py
  61. 2
      slither/core/expressions/conditional_expression.py
  62. 20
      slither/core/expressions/expression_typed.py
  63. 76
      slither/core/expressions/identifier.py
  64. 19
      slither/core/expressions/index_access.py
  65. 4
      slither/core/expressions/literal.py
  66. 3
      slither/core/expressions/member_access.py
  67. 11
      slither/core/expressions/type_conversion.py
  68. 3
      slither/core/expressions/unary_operation.py
  69. 10
      slither/core/slither_core.py
  70. 15
      slither/core/solidity_types/array_type.py
  71. 6
      slither/core/solidity_types/elementary_type.py
  72. 4
      slither/core/solidity_types/mapping_type.py
  73. 8
      slither/core/solidity_types/type_alias.py
  74. 6
      slither/core/solidity_types/type_information.py
  75. 6
      slither/core/source_mapping/source_mapping.py
  76. 5
      slither/core/variables/event_variable.py
  77. 19
      slither/core/variables/local_variable.py
  78. 4
      slither/core/variables/state_variable.py
  79. 19
      slither/core/variables/structure_variable.py
  80. 18
      slither/core/variables/variable.py
  81. 4
      slither/detectors/abstract_detector.py
  82. 13
      slither/detectors/assembly/shift_parameter_mixup.py
  83. 12
      slither/detectors/attributes/const_functions_asm.py
  84. 9
      slither/detectors/attributes/const_functions_state.py
  85. 15
      slither/detectors/attributes/constant_pragma.py
  86. 18
      slither/detectors/attributes/incorrect_solc.py
  87. 8
      slither/detectors/attributes/locked_ether.py
  88. 8
      slither/detectors/attributes/unimplemented_interface.py
  89. 20
      slither/detectors/compiler_bugs/array_by_reference.py
  90. 11
      slither/detectors/compiler_bugs/enum_conversion.py
  91. 11
      slither/detectors/compiler_bugs/multiple_constructor_schemes.py
  92. 3
      slither/detectors/compiler_bugs/reused_base_constructor.py
  93. 9
      slither/detectors/compiler_bugs/storage_ABIEncoderV2_array.py
  94. 32
      slither/detectors/compiler_bugs/storage_signed_integer_array.py
  95. 5
      slither/detectors/compiler_bugs/uninitialized_function_ptr_in_constructor.py
  96. 8
      slither/detectors/erc/erc20/arbitrary_send_erc20.py
  97. 8
      slither/detectors/erc/erc20/arbitrary_send_erc20_no_permit.py
  98. 8
      slither/detectors/erc/erc20/arbitrary_send_erc20_permit.py
  99. 8
      slither/detectors/erc/erc20/incorrect_erc20_interface.py
  100. 12
      slither/detectors/erc/incorrect_erc721_interface.py
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,38 @@
---
body:
-
attributes:
label: "What operating system are you using?"
id: os
type: textarea
validations:
required: true
-
attributes:
label: "How did you install slither?"
description: |
For example, using git or python's pip.
id: install-method
type: textarea
validations:
required: true
- type: dropdown
id: python
attributes:
label: Do you have python added to your $PATH?
multiple: true
options:
- "Yes"
- "No"
- "Not sure"
-
attributes:
description: |
Please copy and paste any relevant log output. This
will be automatically formatted into code, so no need for backticks.
render: shell
label: "Output of running `slither-doctor .`:"
id: logs
labels:
- installation-help
title: "[Installation-Help]: "

@ -1,55 +0,0 @@
---
body:
-
attributes:
value: |
Please check the issues tab to avoid duplicates.
Thanks for taking the time to fill out this bug report!
type: markdown
-
attributes:
label: "What operating system are you using?"
id: os
type: textarea
validations:
required: true
-
attributes:
label: "How did you install slither?"
description: |
For example, using git or python's pip.
id: install-method
type: textarea
validations:
required: true
- type: dropdown
id: python
attributes:
label: Do you have python added to your $PATH?
multiple: true
options:
- "Yes"
- "No"
- "Not sure"
- type: dropdown
id: solc
attributes:
label: Do you have solc-select installed?
multiple: true
options:
- "Yes"
- "No"
-
attributes:
description: |
Please copy and paste any relevant log output. This
will be automatically formatted into code, so no need for backticks.
render: shell
label: "Output of running `slither-doctor .`:"
id: logs
type: textarea
description: "Get help troubleshooting slither installation"
labels:
- installation-help
name: "Trouble with Installing Slither"
title: "[Installation-Help]: "

@ -57,5 +57,5 @@ body:
description: "Slither missed a bug it should find."
labels:
- false-negative
name: False Negative"
name: False Negative
title: "[False Negative]: "

@ -0,0 +1,30 @@
# Derived from <https://github.com/pyca/cryptography/blob/SOME_REF/.github/actions/upload-coverage/action.yml>
# Originally authored by the PyCA Cryptography maintainers, and licensed under
# the terms of the BSD license:
# <https://github.com/pyca/cryptography/blob/main/LICENSE.BSD>
name: Upload Coverage
description: Upload coverage files
runs:
using: "composite"
steps:
# FIXME(jl): codecov has the option of including machine information in filename that would solve this unique naming
# issue more completely.
# This method has the limitation of 1 coverage file per run, limiting some coverage between online/offline tests.
- run: |
COVERAGE_UUID=$(python3 -c "import uuid; print(uuid.uuid4())")
echo "COVERAGE_UUID=${COVERAGE_UUID}" >> $GITHUB_OUTPUT
if [ -f .coverage ]; then
mv .coverage .coverage.${COVERAGE_UUID}
fi
id: coverage-uuid
shell: bash
- uses: actions/upload-artifact@v3.1.0
with:
name: coverage-data
path: |
.coverage.*
*.lcov
if-no-files-found: ignore

@ -0,0 +1,13 @@
#!/usr/bin/env bash
# used to pass --cov=$path and --cov-append to pytest
if [ "$1" != "" ]; then
pytest "$1" tests/e2e/ -n auto
status_code=$?
python -m coverage report
else
pytest tests/e2e/ -n auto
status_code=$?
fi
exit "$status_code"

@ -0,0 +1,13 @@
#!/usr/bin/env bash
# used to pass --cov=$path and --cov-append to pytest
if [ "$1" != "" ]; then
pytest "$1" tests/tools/read-storage/test_read_storage.py
status_code=$?
python -m coverage report
else
pytest tests/tools/read-storage/test_read_storage.py
status_code=$?
fi
exit "$status_code"

@ -0,0 +1,13 @@
#!/usr/bin/env bash
# used to pass --cov=$path and --cov-append to pytest
if [ "$1" != "" ]; then
pytest "$1" tests/unit/ -n auto
status_code=$?
python -m coverage report
else
pytest tests/unit/ -n auto
status_code=$?
fi
exit "$status_code"

@ -1,51 +0,0 @@
---
name: IR tests
defaults:
run:
# To load bashrc
shell: bash -ieo pipefail {0}
on:
pull_request:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: IR tests
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-2022]
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.8
- name: Install dependencies
run: |
pip install ".[dev]"
solc-select install 0.5.0
solc-select use 0.8.11 --always-install
- name: Install old solc
if: matrix.os == 'ubuntu-latest'
run: solc-select install 0.4.0
- name: Test with pytest
run: |
pytest tests/test_ssa_generation.py

@ -42,7 +42,7 @@ jobs:
cp pyproject.toml .github/linters
- name: Black
uses: github/super-linter/slim@v4.9.2
uses: super-linter/super-linter/slim@v4.9.2
if: always()
env:
# run linter on everything to catch preexisting problems

@ -58,12 +58,10 @@ jobs:
python-version: 3.8
- name: Install dependencies
run: |
pip install ".[dev]"
pip install ".[test]"
solc-select use 0.4.25 --always-install
solc-select use 0.8.0 --always-install
solc-select use 0.5.1 --always-install
pip install typing_extensions==4.1.1
pip install importlib_metadata==4.8.3
- name: Set up nix
if: matrix.type == 'dapp'

@ -1,45 +0,0 @@
---
name: Detectors tests
defaults:
run:
# To load bashrc
shell: bash -ieo pipefail {0}
on:
pull_request:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Detectors tests
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-2022]
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.8
- name: Install dependencies
run: |
pip install ".[dev]"
solc-select use 0.7.3 --always-install
- name: Test with pytest
run: |
pytest tests/test_detectors.py

@ -23,7 +23,7 @@ jobs:
# Single deploy job since we're just deploying
build:
environment:
name: github-pages
name: Slither Documentation
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
@ -34,13 +34,13 @@ jobs:
- uses: actions/setup-python@v4
with:
python-version: '3.8'
- run: pip install -e ".[dev]"
- run: pdoc -o docs/ slither '!slither.tools' #TODO fix import errors on pdoc run
- run: pip install -e ".[doc]"
- run: pdoc -o html/ slither '!slither.tools' #TODO fix import errors on pdoc run
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
# Upload the doc
path: 'docs/'
path: './html/'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1

@ -1,56 +0,0 @@
---
name: Features tests
defaults:
run:
# To load bashrc
shell: bash -ieo pipefail {0}
on:
pull_request:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Features tests
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-2022]
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.8
- name: Install dependencies
run: |
pip install ".[dev]"
solc-select use 0.8.0 --always-install
cd tests/test_node_modules/
npm install hardhat
cd ../..
- name: Test with pytest
run: |
pytest tests/test_features.py
pytest tests/test_constant_folding.py
pytest tests/slithir/test_ternary_expressions.py
pytest tests/slithir/test_operation_reads.py
pytest tests/test_functions_ids.py
pytest tests/test_function.py
pytest tests/test_source_mapping.py

@ -22,7 +22,7 @@ concurrency:
jobs:
build:
name: Pylint
name: Superlinter
runs-on: ubuntu-latest
steps:
@ -43,7 +43,7 @@ jobs:
cp pyproject.toml .github/linters
- name: Lint everything else
uses: github/super-linter/slim@v4.9.2
uses: super-linter/super-linter/slim@v4.9.2
if: always()
env:
# run linter on everything to catch preexisting problems

@ -1,49 +0,0 @@
---
name: Parser tests
defaults:
run:
# To load bashrc
shell: bash -ieo pipefail {0}
on:
pull_request:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Parser tests
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-2022]
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.8
- name: Install dependencies
run: |
pip install ".[dev]"
- name: Install solc
run: |
solc-select install all
solc-select use 0.8.0
- name: Test with pytest
run: |
pytest tests/test_ast_parsing.py -n auto

@ -34,6 +34,6 @@ jobs:
python -m pip install .
- name: Run pip-audit
uses: trailofbits/gh-action-pip-audit@v0.0.4
uses: pypa/gh-action-pip-audit@v1.0.7
with:
virtual-environment: /tmp/pip-audit-env

@ -37,7 +37,7 @@ jobs:
cp pyproject.toml .github/linters
- name: Pylint
uses: github/super-linter/slim@v4.9.2
uses: super-linter/super-linter/slim@v4.9.2
if: always()
env:
# Run linters only on new files for pylint to speed up the CI

@ -1,53 +0,0 @@
---
name: Test slither-read-storage
defaults:
run:
# To load bashrc
shell: bash -ieo pipefail {0}
on:
pull_request:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Test slither-read-storage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install ganache
run: npm install --global ganache
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install python dependencies
run: |
pip install ".[dev]"
solc-select install 0.8.1
solc-select install 0.8.10
solc-select use 0.8.1
- name: Run slither-read-storage
run: |
pytest tests/test_read_storage.py
- name: Run storage layout tests
run: |
pytest tests/test_storage_layout.py

@ -0,0 +1,105 @@
---
name: Pytest
defaults:
run:
shell: bash
on:
push:
branches: [master, dev]
pull_request:
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
tests:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-2022"]
type: ["unit", "integration", "tool"]
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v4
with:
python-version: 3.8
cache: "pip"
cache-dependency-path: setup.py
- name: Install dependencies
run: |
pip install ".[test]"
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Install test dependencies
run: |
if [ ${{ matrix.type }} = "tool" ]; then
# Setup Ganache for slither-read-storage tests.
npm install --global ganache
elif [ ${{ matrix.type }} = "integration" ]; then
# Setup Hardhat for compilation tests.
pushd tests/e2e/compilation/test_data/test_node_modules/ || exit
npm install hardhat
popd || exit
fi
- name: Run ${{ matrix.type }} tests
env:
TEST_TYPE: ${{ matrix.type }}
# Only run coverage on ubuntu-latest.
run: |
if [ ${{ matrix.os }} = "ubuntu-latest" ]; then
TEST_ARGS="--cov=slither --cov-append"
elif [ ${{ matrix.os }} = "windows-2022" ]; then
TEST_ARGS=""
fi
bash "./.github/scripts/${TEST_TYPE}_test_runner.sh" $TEST_ARGS
- name: Upload coverage
uses: ./.github/actions/upload-coverage
# only aggregate test coverage over linux-based tests to avoid any OS-specific filesystem information stored in
# coverage metadata.
if: ${{ matrix.os == 'ubuntu-latest' }}
coverage:
needs:
- tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v4
with:
python-version: 3.8
- run: pip install coverage[toml]
- name: download coverage data
uses: actions/download-artifact@v3.0.2
with:
name: coverage-data
- name: combine coverage data
id: combinecoverage
run: |
set +e
python -m coverage combine
echo "## python coverage" >> $GITHUB_STEP_SUMMARY
python -m coverage report -m --format=markdown >> $GITHUB_STEP_SUMMARY

1
.gitignore vendored

@ -42,6 +42,7 @@ htmlcov/
.coverage
.coverage.*
.cache
.pytest_cache
nosetests.xml
coverage.xml
*.cover

@ -1,15 +1,19 @@
# Contributing to Slither
First, thanks for your interest in contributing to Slither! We welcome and appreciate all contributions, including bug reports, feature suggestions, tutorials/blog posts, and code improvements.
If you're unsure where to start, we recommend our [`good first issue`](https://github.com/crytic/slither/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) and [`help wanted`](https://github.com/crytic/slither/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) issue labels.
## 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.
## Questions
Questions can be submitted to the issue tracker, but you may get a faster response if you ask in our [chat room](https://empireslacking.herokuapp.com/) (in the #ethereum channel).
Questions can be submitted to the "Discussions" page, and you may also join our [chat room](https://empireslacking.herokuapp.com/) (in the #ethereum channel).
## 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/).
Some pull request guidelines:
@ -23,6 +27,7 @@ Some pull request guidelines:
## Directory Structure
Below is a rough outline of slither's design:
```text
.
├── analyses # Provides additional info such as data dependency
@ -39,9 +44,10 @@ Below is a rough outline of slither's design:
A code walkthrough is available [here](https://www.youtube.com/watch?v=EUl3UlYSluU).
## Development Environment
Instructions for installing a development version of Slither can be found in our [wiki](https://github.com/crytic/slither/wiki/Developer-installation).
To run the unit tests, you need to clone this repository and run `pip install ".[dev]"`.
To run the unit tests, you need to clone this repository and run `make test`. Run a specific test with `make test TESTS=$test_name`. The names of tests can be obtained with `pytest tests --collect-only`.
### Linters
@ -49,40 +55,63 @@ Several linters and security checkers are run on the PRs.
To run them locally in the root dir of the repository:
- `pylint slither tests --rcfile pyproject.toml`
- `black . --config pyproject.toml`
- `make lint`
> Note, this only validates but does not modify the code.
To automatically reformat the code:
- `make reformat`
We use pylint `2.13.4`, black `22.3.0`.
### Detectors tests
### Testing
Slither's test suite is divided into three categories end-to-end (`tests/e2e`), unit (`tests/unit`), and tools (`tests/tools/`).
How do I know what kind of test(s) to write?
- End-to-end: functionality that requires invoking `Slither` and inspecting some output such as printers and detectors.
- Unit: additions and modifications to objects should be accompanied by a unit test that defines the expected behavior. Aim to write functions in as pure a way as possible such that they are easier to test.
- Tools: tools built on top of Slither (`slither/tools`) but not apart of its core functionality
#### Adding detector tests
For each new detector, at least one regression tests must be present.
- Create a test in `tests`
- Update `ALL_TEST` in `tests/test_detectors.py`
- Run `python ./tests/test_detectors.py --generate`. This will generate the json artifacts in `tests/expected_json`. Add the generated files to git.
- If updating an existing detector, identify the respective json artifacts and then delete them, or run `python ./tests/test_detectors.py --overwrite` instead.
- Run `pytest ./tests/test_detectors.py` and check that everything worked.
To see the tests coverage, run `pytest tests/test_detectors.py --cov=slither/detectors --cov-branch --cov-report html`.
To run tests for a specific detector, run `pytest tests/test_detectors.py -k ReentrancyReadBeforeWritten` (the detector's class name is the argument).
To run tests for a specific version, run `pytest tests/test_detectors.py -k 0.7.6`.
The IDs of tests can be inspected using `pytest tests/test_detectors.py --collect-only`.
### Parser tests
- Create a test in `tests/ast-parsing`
- Run `python ./tests/test_ast_parsing.py --compile`. This will compile the artifact in `tests/ast-parsing/compile`. Add the compiled artifact to git.
- Run `python ./tests/test_ast_parsing.py --generate`. This will generate the json artifacts in `tests/ast-parsing/expected_json`. Add the generated files to git.
- Run `pytest ./tests/test_ast_parsing.py` and check that everything worked.
To see the tests coverage, run `pytest tests/test_ast_parsing.py --cov=slither/solc_parsing --cov-branch --cov-report html`
To run tests for a specific test case, run `pytest tests/test_ast_parsing.py -k user_defined_value_type` (the filename is the argument).
To run tests for a specific version, run `pytest tests/test_ast_parsing.py -k 0.8.12`.
To run tests for a specific compiler json format, run `pytest tests/test_ast_parsing.py -k legacy` (can be legacy or compact).
The IDs of tests can be inspected using ``pytest tests/test_ast_parsing.py --collect-only`.
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_TEST` 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.
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.
> ##### Helpful commands for detector tests
>
> - To see the tests coverage, run `pytest tests/e2e/detectors/test_detectors.py --cov=slither/detectors --cov-branch --cov-report html`.
> - To run tests for a specific detector, run `pytest tests/e2e/detectors/test_detectors.py -k ReentrancyReadBeforeWritten`(the detector's class name is the argument).
> - To run tests for a specific version, run `pytest tests/e2e/detectors/test_detectors.py -k 0.7.6`.
> - The IDs of tests can be inspected using `pytest tests/e2e/detectors/test_detectors.py --collect-only`.
#### Adding parsing tests
1. Create a test in `tests/e2e/solc_parsing/`
2. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --compile`. This will compile the artifact in `tests/e2e/solc_parsing/compile`. Add the compiled artifact to git.
3. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --generate`. This will generate the json artifacts in `tests/e2e/solc_parsing/expected_json`. Add the generated files to git.
4. Run `pytest tests/e2e/solc_parsing/test_ast_parsing.py` and check that everything worked.
> ##### Helpful commands for parsing tests
>
> - To see the tests coverage, run `pytest tests/e2e/solc_parsing/test_ast_parsing.py --cov=slither/solc_parsing --cov-branch --cov-report html`
> - To run tests for a specific test case, run `pytest tests/e2e/solc_parsing/test_ast_parsing.py -k user_defined_value_type` (the filename is the argument).
> - To run tests for a specific version, run `pytest tests/e2e/solc_parsing/test_ast_parsing.py -k 0.8.12`.
> - To run tests for a specific compiler json format, run `pytest tests/e2e/solc_parsing/test_ast_parsing.py -k legacy` (can be legacy or compact).
> - The IDs of tests can be inspected using ``pytest tests/e2e/solc_parsing/test_ast_parsing.py --collect-only`.
### Synchronization with crytic-compile
By default, `slither` follows either the latest version of crytic-compile in pip, or `crytic-compile@master` (look for dependencies in [`setup.py`](./setup.py). If crytic-compile development comes with breaking changes, the process to update `slither` is:
- Update `slither/setup.py` to point to the related crytic-compile's branch
- Create a PR in `slither` and ensure it passes the CI
- Once the development branch is merged in `crytic-compile@master`, ensure `slither` follows the `master` branch

@ -0,0 +1,88 @@
SHELL := /bin/bash
PY_MODULE := slither
TEST_MODULE := tests
ALL_PY_SRCS := $(shell find $(PY_MODULE) -name '*.py') \
$(shell find test -name '*.py')
# Optionally overriden by the user, if they're using a virtual environment manager.
VENV ?= env
# On Windows, venv scripts/shims are under `Scripts` instead of `bin`.
VENV_BIN := $(VENV)/bin
ifeq ($(OS),Windows_NT)
VENV_BIN := $(VENV)/Scripts
endif
# Optionally overridden by the user in the `release` target.
BUMP_ARGS :=
# Optionally overridden by the user in the `test` target.
TESTS :=
# Optionally overridden by the user/CI, to limit the installation to a specific
# subset of development dependencies.
SLITHER_EXTRA := dev
# If the user selects a specific test pattern to run, set `pytest` to fail fast
# and only run tests that match the pattern.
# Otherwise, run all tests and enable coverage assertions, since we expect
# complete test coverage.
ifneq ($(TESTS),)
TEST_ARGS := -x -k $(TESTS)
COV_ARGS :=
else
TEST_ARGS := -n auto
COV_ARGS := # --fail-under 100
endif
.PHONY: all
all:
@echo "Run my targets individually!"
.PHONY: dev
dev: $(VENV)/pyvenv.cfg
.PHONY: run
run: $(VENV)/pyvenv.cfg
@. $(VENV_BIN)/activate && slither $(ARGS)
$(VENV)/pyvenv.cfg: pyproject.toml
# Create our Python 3 virtual environment
python3 -m venv env
$(VENV_BIN)/python -m pip install --upgrade pip
$(VENV_BIN)/python -m pip install -e .[$(SLITHER_EXTRA)]
.PHONY: lint
lint: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
black --check . && \
pylint $(PY_MODULE) $(TEST_MODULE)
# ruff $(ALL_PY_SRCS) && \
# mypy $(PY_MODULE) &&
.PHONY: reformat
reformat:
. $(VENV_BIN)/activate && \
black .
.PHONY: test tests
test tests: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
pytest --cov=$(PY_MODULE) $(T) $(TEST_ARGS) && \
python -m coverage report -m $(COV_ARGS)
.PHONY: doc
doc: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
PDOC_ALLOW_EXEC=1 pdoc -o html slither '!slither.tools'
.PHONY: package
package: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
python3 -m build
.PHONY: edit
edit:
$(EDITOR) $(ALL_PY_SRCS)

@ -151,26 +151,27 @@ Num | Detector | What it Detects | Impact | Confidence
61 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
62 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High
63 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High
64 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
65 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
66 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state) | Informational | High
67 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
68 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | Informational | High
69 | `naming-convention` | [Conformity to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
70 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
71 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
72 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
73 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
74 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High
75 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
76 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
77 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
78 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
79 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
80 | `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
81 | `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
82 | `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
83 | `var-read-using-this` | [Contract reads its own variable using `this`](https://github.com/crytic/slither/wiki/Vulnerabilities-Description#public-variable-read-in-external-context) | Optimization | High
64 | `cyclomatic-complexity` | [Detects functions with high (> 11) cyclomatic complexity](https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity) | Informational | High
65 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
66 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
67 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state) | Informational | High
68 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
69 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | Informational | High
70 | `naming-convention` | [Conformity to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
71 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
72 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
73 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
74 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
75 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High
76 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
77 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
78 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
79 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
80 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
81 | `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
82 | `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
83 | `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
84 | `var-read-using-this` | [Contract reads its own variable using `this`](https://github.com/crytic/slither/wiki/Vulnerabilities-Description#public-variable-read-in-external-context) | Optimization | High
For more information, see
- The [Detector Documentation](https://github.com/crytic/slither/wiki/Detector-Documentation) for details on each detector
@ -246,7 +247,7 @@ Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailt
- [Slither: A Static Analysis Framework For Smart Contracts](https://arxiv.org/abs/1908.09878), Josselin Feist, Gustavo Grieco, Alex Groce - WETSEB '19
### External publications
Title | Usage | Authors | Venue | Code
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
[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)

@ -18,6 +18,8 @@ assert len(contracts) == 1
contract = contracts[0]
destination = contract.get_state_variable_from_name("destination")
source = contract.get_state_variable_from_name("source")
assert source
assert destination
print(f"{source} is dependent of {destination}: {is_dependent(source, destination, contract)}")
assert not is_dependent(source, destination, contract)
@ -47,9 +49,11 @@ print(f"{destination} is tainted {is_tainted(destination, contract)}")
assert is_tainted(destination, contract)
destination_indirect_1 = contract.get_state_variable_from_name("destination_indirect_1")
assert destination_indirect_1
print(f"{destination_indirect_1} is tainted {is_tainted(destination_indirect_1, contract)}")
assert is_tainted(destination_indirect_1, contract)
destination_indirect_2 = contract.get_state_variable_from_name("destination_indirect_2")
assert destination_indirect_2
print(f"{destination_indirect_2} is tainted {is_tainted(destination_indirect_2, contract)}")
assert is_tainted(destination_indirect_2, contract)
@ -88,6 +92,8 @@ contract = contracts[0]
contract_derived = slither.get_contract_from_name("Derived")[0]
destination = contract.get_state_variable_from_name("destination")
source = contract.get_state_variable_from_name("source")
assert destination
assert source
print(f"{destination} is dependent of {source}: {is_dependent(destination, source, contract)}")
assert not is_dependent(destination, source, contract)

@ -14,6 +14,7 @@ assert len(contracts) == 1
contract = contracts[0]
# Get the variable
var_a = contract.get_state_variable_from_name("a")
assert var_a
# Get the functions reading the variable
functions_reading_a = contract.get_functions_reading_from_variable(var_a)

@ -4,17 +4,17 @@
solc-select use 0.7.0
if ! slither "tests/config/test.sol" --solc-ast --no-fail-pedantic; then
if ! slither "tests/e2e/config/test_json_config/test.sol" --solc-ast --no-fail-pedantic; then
echo "--solc-ast failed"
exit 1
fi
if ! slither "tests/config/test.sol" --solc-disable-warnings --no-fail-pedantic; then
if ! slither "tests/e2e/config/test_json_config/test.sol" --solc-disable-warnings --no-fail-pedantic; then
echo "--solc-disable-warnings failed"
exit 1
fi
if ! slither "tests/config/test.sol" --disable-color --no-fail-pedantic; then
if ! slither "tests/e2e/config/test_json_config/test.sol" --disable-color --no-fail-pedantic; then
echo "--disable-color failed"
exit 1
fi

@ -2,7 +2,7 @@
### Test slither-check-erc
DIR_TESTS="tests/check-erc"
DIR_TESTS="tests/tools/check-erc"
solc-select use 0.5.0
slither-check-erc "$DIR_TESTS/erc20.sol" ERC20 > test_1.txt 2>&1

@ -3,7 +3,7 @@
### Test path filtering across POSIX and Windows
solc-select use 0.8.0
slither "tests/test_path_filtering/test_path_filtering.sol" --config "tests/test_path_filtering/slither.config.json" > "output.txt" 2>&1
slither "tests/e2e/config/test_path_filtering/test_path_filtering.sol" --config "tests/e2e/config/test_path_filtering/slither.config.json" > "output.txt" 2>&1
if ! grep -q "0 result(s) found" "output.txt"
then

@ -2,7 +2,7 @@
### Test printer
cd tests/ast-parsing/compile || exit
cd tests/e2e/solc_parsing/test_data/compile/ || exit
# Do not test the evm printer,as it needs a refactoring
ALL_PRINTERS="cfg,constructor-calls,contract-summary,data-dependency,echidna,function-id,function-summary,modifiers,call-graph,human-summary,inheritance,inheritance-graph,slithir,slithir-ssa,vars-and-auth,require,variable-order,declaration"

@ -8,28 +8,39 @@ setup(
description="Slither is a Solidity static analysis framework written in Python 3.",
url="https://github.com/crytic/slither",
author="Trail of Bits",
version="0.9.2",
version="0.9.3",
packages=find_packages(),
python_requires=">=3.8",
install_requires=[
"packaging",
"prettytable>=0.7.2",
"pycryptodome>=3.4.6",
# "crytic-compile>=0.3.0",
"crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile",
# "crytic-compile>=0.3.1,<0.4.0",
"crytic-compile@git+https://github.com/crytic/crytic-compile.git@windows-rel-path#egg=crytic-compile",
"web3>=6.0.0",
],
extras_require={
"dev": [
"lint": [
"black==22.3.0",
"pylint==2.13.4",
],
"test": [
"pytest",
"pytest-cov",
"pytest-xdist",
"deepdiff",
"numpy",
"openai",
"coverage[toml]",
"filelock",
"pytest-insta",
"solc-select@git+https://github.com/crytic/solc-select.git@query-artifact-path#egg=solc-select",
],
"doc": [
"pdoc",
"web3>=6.0.0",
],
"dev": [
"slither-analyzer[lint,test,doc]",
"openai",
],
},
license="AGPL-3.0",

@ -1 +1,4 @@
"""
.. include:: ../README.md
"""
from .slither import Slither

@ -66,7 +66,7 @@ def process_single(
args: argparse.Namespace,
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[Slither, List[Dict], List[Dict], int]:
) -> Tuple[Slither, List[Dict], List[Output], int]:
"""
The core high-level code for running Slither static analysis.
@ -86,7 +86,7 @@ def process_all(
args: argparse.Namespace,
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[List[Slither], List[Dict], List[Dict], int]:
) -> Tuple[List[Slither], List[Dict], List[Output], int]:
compilations = compile_all(target, **vars(args))
slither_instances = []
results_detectors = []
@ -141,23 +141,6 @@ def _process(
return slither, results_detectors, results_printers, analyzed_contracts_count
# TODO: delete me?
def process_from_asts(
filenames: List[str],
args: argparse.Namespace,
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[Slither, List[Dict], List[Dict], int]:
all_contracts: List[str] = []
for filename in filenames:
with open(filename, encoding="utf8") as file_open:
contract_loaded = json.load(file_open)
all_contracts.append(contract_loaded["ast"])
return process_single(all_contracts, args, detector_classes, printer_classes)
# endregion
###################################################################################
###################################################################################
@ -605,9 +588,6 @@ def parse_args(
default=False,
)
# if the json is splitted in different files
parser.add_argument("--splitted", help=argparse.SUPPRESS, action="store_true", default=False)
# Disable the throw/catch on partial analyses
parser.add_argument(
"--disallow-partial", help=argparse.SUPPRESS, action="store_true", default=False
@ -623,7 +603,7 @@ def parse_args(
args.filter_paths = parse_filter_paths(args)
# Verify our json-type output is valid
args.json_types = set(args.json_types.split(","))
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.')
@ -632,7 +612,9 @@ def parse_args(
class ListDetectors(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(self, parser, *args, **kwargs): # pylint: disable=signature-differs
def __call__(
self, parser: Any, *args: Any, **kwargs: Any
) -> None: # pylint: disable=signature-differs
detectors, _ = get_detectors_and_printers()
output_detectors(detectors)
parser.exit()
@ -694,14 +676,14 @@ class OutputWiki(argparse.Action): # pylint: disable=too-few-public-methods
class FormatterCryticCompile(logging.Formatter):
def format(self, record):
def format(self, record: logging.LogRecord) -> str:
# for i, msg in enumerate(record.msg):
if record.msg.startswith("Compilation warnings/errors on "):
txt = record.args[1]
txt = txt.split("\n")
txt = record.args[1] # type:ignore
txt = txt.split("\n") # type:ignore
txt = [red(x) if "Error" in x else x for x in txt]
txt = "\n".join(txt)
record.args = (record.args[0], txt)
record.args = (record.args[0], txt) # type:ignore
return super().format(record)
@ -744,7 +726,7 @@ def main_impl(
set_colorization_enabled(False if args.disable_color else sys.stdout.isatty())
# Define some variables for potential JSON output
json_results = {}
json_results: Dict[str, Any] = {}
output_error = None
outputting_json = args.json is not None
outputting_json_stdout = args.json == "-"
@ -793,7 +775,7 @@ def main_impl(
crytic_compile_error.setLevel(logging.INFO)
results_detectors: List[Dict] = []
results_printers: List[Dict] = []
results_printers: List[Output] = []
try:
filename = args.filename
@ -806,26 +788,17 @@ def main_impl(
number_contracts = 0
slither_instances = []
if args.splitted:
for filename in filenames:
(
slither_instance,
results_detectors,
results_printers,
number_contracts,
) = process_from_asts(filenames, args, detector_classes, printer_classes)
results_detectors_tmp,
results_printers_tmp,
number_contracts_tmp,
) = process_single(filename, args, detector_classes, printer_classes)
number_contracts += number_contracts_tmp
results_detectors += results_detectors_tmp
results_printers += results_printers_tmp
slither_instances.append(slither_instance)
else:
for filename in filenames:
(
slither_instance,
results_detectors_tmp,
results_printers_tmp,
number_contracts_tmp,
) = process_single(filename, args, detector_classes, printer_classes)
number_contracts += number_contracts_tmp
results_detectors += results_detectors_tmp
results_printers += results_printers_tmp
slither_instances.append(slither_instance)
# Rely on CryticCompile to discern the underlying type of compilations.
else:

@ -2,8 +2,9 @@
Compute the data depenency between all the SSA variables
"""
from collections import defaultdict
from typing import Union, Set, Dict, TYPE_CHECKING
from typing import Union, Set, Dict, TYPE_CHECKING, List
from slither.core.cfg.node import Node
from slither.core.declarations import (
Contract,
Enum,
@ -12,11 +13,14 @@ from slither.core.declarations import (
SolidityVariable,
SolidityVariableComposed,
Structure,
FunctionContract,
)
from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder
from slither.core.solidity_types.type import Type
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.core.variables.variable import Variable
from slither.slithir.operations import Index, OperationWithLValue, InternalCall, Operation
from slither.slithir.utils.utils import LVALUE
from slither.slithir.variables import (
Constant,
LocalIRVariable,
@ -26,12 +30,11 @@ from slither.slithir.variables import (
TemporaryVariableSSA,
TupleVariableSSA,
)
from slither.core.solidity_types.type import Type
from slither.slithir.variables.variable import SlithIRVariable
if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
###################################################################################
###################################################################################
# region User APIs
@ -39,26 +42,39 @@ if TYPE_CHECKING:
###################################################################################
Variable_types = Union[Variable, SolidityVariable]
SUPPORTED_TYPES = Union[Variable, SolidityVariable]
# TODO refactor the data deps to be better suited for top level function object
# Right now we allow to pass a node to ease the API, but we need something
# better
# The deps propagation for top level elements is also not working as expected
Context_types_API = Union[Contract, Function, Node]
Context_types = Union[Contract, Function]
def is_dependent(
variable: Variable_types,
source: Variable_types,
context: Context_types,
variable: SUPPORTED_TYPES,
source: SUPPORTED_TYPES,
context: Context_types_API,
only_unprotected: bool = False,
) -> bool:
"""
If Node is provided as context, the context will be the broader context, either the contract or the function,
depending on if the node is in a top level function or not
Args:
variable (Variable)
source (Variable)
context (Contract|Function)
context (Contract|Function|Node).
only_unprotected (bool): True only unprotected function are considered
Returns:
bool
"""
assert isinstance(context, (Contract, Function))
assert isinstance(context, (Contract, Function, Node))
if isinstance(context, Node):
func = context.function
context = func.contract if isinstance(func, FunctionContract) else func
if isinstance(variable, Constant):
return False
if variable == source:
@ -74,12 +90,15 @@ def is_dependent(
def is_dependent_ssa(
variable: Variable_types,
source: Variable_types,
context: Context_types,
variable: SUPPORTED_TYPES,
source: SUPPORTED_TYPES,
context: Context_types_API,
only_unprotected: bool = False,
) -> bool:
"""
If Node is provided as context, the context will be the broader context, either the contract or the function,
depending on if the node is in a top level function or not
Args:
variable (Variable)
taint (Variable)
@ -88,7 +107,10 @@ def is_dependent_ssa(
Returns:
bool
"""
assert isinstance(context, (Contract, Function))
assert isinstance(context, (Contract, Function, Node))
if isinstance(context, Node):
func = context.function
context = func.contract if isinstance(func, FunctionContract) else func
context_dict = context.context
if isinstance(variable, Constant):
return False
@ -112,12 +134,15 @@ GENERIC_TAINT = {
def is_tainted(
variable: Variable_types,
context: Context_types,
variable: SUPPORTED_TYPES,
context: Context_types_API,
only_unprotected: bool = False,
ignore_generic_taint: bool = False,
) -> bool:
"""
If Node is provided as context, the context will be the broader context, either the contract or the function,
depending on if the node is in a top level function or not
Args:
variable
context (Contract|Function)
@ -125,7 +150,10 @@ def is_tainted(
Returns:
bool
"""
assert isinstance(context, (Contract, Function))
assert isinstance(context, (Contract, Function, Node))
if isinstance(context, Node):
func = context.function
context = func.contract if isinstance(func, FunctionContract) else func
assert isinstance(only_unprotected, bool)
if isinstance(variable, Constant):
return False
@ -139,12 +167,15 @@ def is_tainted(
def is_tainted_ssa(
variable: Variable_types,
context: Context_types,
variable: SUPPORTED_TYPES,
context: Context_types_API,
only_unprotected: bool = False,
ignore_generic_taint: bool = False,
):
) -> bool:
"""
If Node is provided as context, the context will be the broader context, either the contract or the function,
depending on if the node is in a top level function or not
Args:
variable
context (Contract|Function)
@ -152,7 +183,10 @@ def is_tainted_ssa(
Returns:
bool
"""
assert isinstance(context, (Contract, Function))
assert isinstance(context, (Contract, Function, Node))
if isinstance(context, Node):
func = context.function
context = func.contract if isinstance(func, FunctionContract) else func
assert isinstance(only_unprotected, bool)
if isinstance(variable, Constant):
return False
@ -166,19 +200,24 @@ def is_tainted_ssa(
def get_dependencies(
variable: Variable_types,
context: Context_types,
variable: SUPPORTED_TYPES,
context: Context_types_API,
only_unprotected: bool = False,
) -> Set[Variable]:
"""
Return the variables for which `variable` depends on.
If Node is provided as context, the context will be the broader context, either the contract or the function,
depending on if the node is in a top level function or not
:param variable: The target
:param context: Either a function (interprocedural) or a contract (inter transactional)
:param only_unprotected: True if consider only protected functions
:return: set(Variable)
"""
assert isinstance(context, (Contract, Function))
assert isinstance(context, (Contract, Function, Node))
if isinstance(context, Node):
func = context.function
context = func.contract if isinstance(func, FunctionContract) else func
assert isinstance(only_unprotected, bool)
if only_unprotected:
return context.context[KEY_NON_SSA_UNPROTECTED].get(variable, set())
@ -186,16 +225,21 @@ def get_dependencies(
def get_all_dependencies(
context: Context_types, only_unprotected: bool = False
context: Context_types_API, only_unprotected: bool = False
) -> Dict[Variable, Set[Variable]]:
"""
Return the dictionary of dependencies.
If Node is provided as context, the context will be the broader context, either the contract or the function,
depending on if the node is in a top level function or not
:param context: Either a function (interprocedural) or a contract (inter transactional)
:param only_unprotected: True if consider only protected functions
:return: Dict(Variable, set(Variable))
"""
assert isinstance(context, (Contract, Function))
assert isinstance(context, (Contract, Function, Node))
if isinstance(context, Node):
func = context.function
context = func.contract if isinstance(func, FunctionContract) else func
assert isinstance(only_unprotected, bool)
if only_unprotected:
return context.context[KEY_NON_SSA_UNPROTECTED]
@ -203,19 +247,24 @@ def get_all_dependencies(
def get_dependencies_ssa(
variable: Variable_types,
context: Context_types,
variable: SUPPORTED_TYPES,
context: Context_types_API,
only_unprotected: bool = False,
) -> Set[Variable]:
"""
Return the variables for which `variable` depends on (SSA version).
If Node is provided as context, the context will be the broader context, either the contract or the function,
depending on if the node is in a top level function or not
:param variable: The target (must be SSA variable)
:param context: Either a function (interprocedural) or a contract (inter transactional)
:param only_unprotected: True if consider only protected functions
:return: set(Variable)
"""
assert isinstance(context, (Contract, Function))
assert isinstance(context, (Contract, Function, Node))
if isinstance(context, Node):
func = context.function
context = func.contract if isinstance(func, FunctionContract) else func
assert isinstance(only_unprotected, bool)
if only_unprotected:
return context.context[KEY_SSA_UNPROTECTED].get(variable, set())
@ -223,16 +272,21 @@ def get_dependencies_ssa(
def get_all_dependencies_ssa(
context: Context_types, only_unprotected: bool = False
context: Context_types_API, only_unprotected: bool = False
) -> Dict[Variable, Set[Variable]]:
"""
Return the dictionary of dependencies.
If Node is provided as context, the context will be the broader context, either the contract or the function,
depending on if the node is in a top level function or not
:param context: Either a function (interprocedural) or a contract (inter transactional)
:param only_unprotected: True if consider only protected functions
:return: Dict(Variable, set(Variable))
"""
assert isinstance(context, (Contract, Function))
assert isinstance(context, (Contract, Function, Node))
if isinstance(context, Node):
func = context.function
context = func.contract if isinstance(func, FunctionContract) else func
assert isinstance(only_unprotected, bool)
if only_unprotected:
return context.context[KEY_SSA_UNPROTECTED]
@ -342,13 +396,9 @@ def transitive_close_dependencies(
while changed:
changed = False
to_add = defaultdict(set)
[ # pylint: disable=expression-not-assigned
[
for key, items in context.context[context_key].items():
for item in items & keys:
to_add[key].update(context.context[context_key][item] - {key} - items)
for item in items & keys
]
for key, items in context.context[context_key].items()
]
for k, v in to_add.items():
# Because we dont have any check on the update operation
# We might update an empty set with an empty set
@ -367,20 +417,20 @@ def add_dependency(lvalue: Variable, function: Function, ir: Operation, is_prote
function.context[KEY_SSA][lvalue] = set()
if not is_protected:
function.context[KEY_SSA_UNPROTECTED][lvalue] = set()
read: Union[List[Union[LVALUE, SolidityVariableComposed]], List[SlithIRVariable]]
if isinstance(ir, Index):
read = [ir.variable_left]
elif isinstance(ir, InternalCall):
elif isinstance(ir, InternalCall) and ir.function:
read = ir.function.return_values_ssa
else:
read = ir.read
# pylint: disable=expression-not-assigned
[function.context[KEY_SSA][lvalue].add(v) for v in read if not isinstance(v, Constant)]
for v in read:
if not isinstance(v, Constant):
function.context[KEY_SSA][lvalue].add(v)
if not is_protected:
[
function.context[KEY_SSA_UNPROTECTED][lvalue].add(v)
for v in read
if not isinstance(v, Constant)
]
for v in read:
if not isinstance(v, Constant):
function.context[KEY_SSA_UNPROTECTED][lvalue].add(v)
def compute_dependency_function(function: Function) -> None:
@ -408,7 +458,7 @@ def compute_dependency_function(function: Function) -> None:
)
def convert_variable_to_non_ssa(v: Variable_types) -> Variable_types:
def convert_variable_to_non_ssa(v: SUPPORTED_TYPES) -> SUPPORTED_TYPES:
if isinstance(
v,
(
@ -439,10 +489,10 @@ def convert_variable_to_non_ssa(v: Variable_types) -> Variable_types:
def convert_to_non_ssa(
data_depencies: Dict[Variable_types, Set[Variable_types]]
) -> Dict[Variable_types, Set[Variable_types]]:
data_depencies: Dict[SUPPORTED_TYPES, Set[SUPPORTED_TYPES]]
) -> Dict[SUPPORTED_TYPES, Set[SUPPORTED_TYPES]]:
# Need to create new set() as its changed during iteration
ret: Dict[Variable_types, Set[Variable_types]] = {}
ret: Dict[SUPPORTED_TYPES, Set[SUPPORTED_TYPES]] = {}
for (k, values) in data_depencies.items():
var = convert_variable_to_non_ssa(k)
if not var in ret:

@ -2,10 +2,10 @@
Detect if all the given variables are written in all the paths of the function
"""
from collections import defaultdict
from typing import Dict, Set, List
from typing import Dict, Set, List, Any, Optional
from slither.core.cfg.node import NodeType, Node
from slither.core.declarations import SolidityFunction
from slither.core.declarations import SolidityFunction, Function
from slither.core.variables.variable import Variable
from slither.slithir.operations import (
Index,
@ -18,7 +18,7 @@ from slither.slithir.variables import ReferenceVariable, TemporaryVariable
class State: # pylint: disable=too-few-public-methods
def __init__(self):
def __init__(self) -> None:
# Map node -> list of variables set
# Were each variables set represents a configuration of a path
# If two paths lead to the exact same set of variables written, we dont need to explore both
@ -34,11 +34,11 @@ class State: # pylint: disable=too-few-public-methods
# pylint: disable=too-many-branches
def _visit(
node: Node,
node: Optional[Node],
state: State,
variables_written: Set[Variable],
variables_to_write: List[Variable],
):
) -> List[Variable]:
"""
Explore all the nodes to look for values not written when the node's function return
Fixpoint reaches if no new written variables are found
@ -51,6 +51,8 @@ def _visit(
refs = {}
variables_written = set(variables_written)
if not node:
return []
for ir in node.irs:
if isinstance(ir, SolidityCall):
# TODO convert the revert to a THROW node
@ -70,17 +72,20 @@ def _visit(
if ir.lvalue and not isinstance(ir.lvalue, (TemporaryVariable, ReferenceVariable)):
variables_written.add(ir.lvalue)
lvalue = ir.lvalue
lvalue: Any = ir.lvalue
while isinstance(lvalue, ReferenceVariable):
if lvalue not in refs:
break
if refs[lvalue] and not isinstance(
refs[lvalue], (TemporaryVariable, ReferenceVariable)
refs_lvalues = refs[lvalue]
if (
refs_lvalues
and isinstance(refs_lvalues, Variable)
and not isinstance(refs_lvalues, (TemporaryVariable, ReferenceVariable))
):
variables_written.add(refs[lvalue])
lvalue = refs[lvalue]
variables_written.add(refs_lvalues)
lvalue = refs_lvalues
ret = []
ret: List[Variable] = []
if not node.sons and node.type not in [NodeType.THROW, NodeType.RETURN]:
ret += [v for v in variables_to_write if v not in variables_written]
@ -96,7 +101,7 @@ def _visit(
return ret
def are_variables_written(function, variables_to_write):
def are_variables_written(function: Function, variables_to_write: List[Variable]) -> List[Variable]:
"""
Return the list of variable that are not written at the end of the function

@ -5,8 +5,7 @@ from enum import Enum
from typing import Optional, List, Set, Dict, Tuple, Union, TYPE_CHECKING
from slither.all_exceptions import SlitherException
from slither.core.children.child_function import ChildFunction
from slither.core.declarations import Contract, Function
from slither.core.declarations import Contract, Function, FunctionContract
from slither.core.declarations.solidity_variables import (
SolidityVariable,
SolidityFunction,
@ -33,6 +32,7 @@ from slither.slithir.operations import (
Return,
Operation,
)
from slither.slithir.utils.utils import RVALUE
from slither.slithir.variables import (
Constant,
LocalIRVariable,
@ -106,7 +106,7 @@ class NodeType(Enum):
# I am not sure why, but pylint reports a lot of "no-member" issue that are not real (Josselin)
# pylint: disable=no-member
class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-methods
class Node(SourceMapping): # pylint: disable=too-many-public-methods
"""
Node class
@ -146,12 +146,12 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._node_id: int = node_id
self._vars_written: List[Variable] = []
self._vars_read: List[Variable] = []
self._vars_read: List[Union[Variable, SolidityVariable]] = []
self._ssa_vars_written: List["SlithIRVariable"] = []
self._ssa_vars_read: List["SlithIRVariable"] = []
self._internal_calls: List["Function"] = []
self._internal_calls: List[Union["Function", "SolidityFunction"]] = []
self._solidity_calls: List[SolidityFunction] = []
self._high_level_calls: List["HighLevelCallType"] = [] # contains library calls
self._library_calls: List["LibraryCallType"] = []
@ -172,7 +172,9 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._local_vars_read: List[LocalVariable] = []
self._local_vars_written: List[LocalVariable] = []
self._slithir_vars: Set["SlithIRVariable"] = set() # non SSA
self._slithir_vars: Set[
Union["SlithIRVariable", ReferenceVariable, TemporaryVariable, TupleVariable]
] = set() # non SSA
self._ssa_local_vars_read: List[LocalIRVariable] = []
self._ssa_local_vars_written: List[LocalIRVariable] = []
@ -189,6 +191,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self.scope: Union["Scope", "Function"] = scope
self.file_scope: "FileScope" = file_scope
self._function: Optional["Function"] = None
###################################################################################
###################################################################################
@ -213,7 +216,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._node_type
@type.setter
def type(self, new_type: NodeType):
def type(self, new_type: NodeType) -> None:
self._node_type = new_type
@property
@ -224,6 +227,13 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return True
return False
def set_function(self, function: "Function") -> None:
self._function = function
@property
def function(self) -> "Function":
return self._function
# endregion
###################################################################################
###################################################################################
@ -232,7 +242,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
###################################################################################
@property
def variables_read(self) -> List[Variable]:
def variables_read(self) -> List[Union[Variable, SolidityVariable]]:
"""
list(Variable): Variables read (local/state/solidity)
"""
@ -285,11 +295,13 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._expression_vars_read
@variables_read_as_expression.setter
def variables_read_as_expression(self, exprs: List[Expression]):
def variables_read_as_expression(self, exprs: List[Expression]) -> None:
self._expression_vars_read = exprs
@property
def slithir_variables(self) -> List["SlithIRVariable"]:
def slithir_variables(
self,
) -> List[Union["SlithIRVariable", ReferenceVariable, TemporaryVariable, TupleVariable]]:
return list(self._slithir_vars)
@property
@ -339,7 +351,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._expression_vars_written
@variables_written_as_expression.setter
def variables_written_as_expression(self, exprs: List[Expression]):
def variables_written_as_expression(self, exprs: List[Expression]) -> None:
self._expression_vars_written = exprs
# endregion
@ -399,7 +411,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._external_calls_as_expressions
@external_calls_as_expressions.setter
def external_calls_as_expressions(self, exprs: List[Expression]):
def external_calls_as_expressions(self, exprs: List[Expression]) -> None:
self._external_calls_as_expressions = exprs
@property
@ -410,7 +422,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._internal_calls_as_expressions
@internal_calls_as_expressions.setter
def internal_calls_as_expressions(self, exprs: List[Expression]):
def internal_calls_as_expressions(self, exprs: List[Expression]) -> None:
self._internal_calls_as_expressions = exprs
@property
@ -418,10 +430,10 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return list(self._expression_calls)
@calls_as_expression.setter
def calls_as_expression(self, exprs: List[Expression]):
def calls_as_expression(self, exprs: List[Expression]) -> None:
self._expression_calls = exprs
def can_reenter(self, callstack=None) -> bool:
def can_reenter(self, callstack: Optional[List[Union[Function, Variable]]] = None) -> bool:
"""
Check if the node can re-enter
Do not consider CREATE as potential re-enter, but check if the
@ -567,7 +579,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
"""
self._fathers.append(father)
def set_fathers(self, fathers: List["Node"]):
def set_fathers(self, fathers: List["Node"]) -> None:
"""Set the father nodes
Args:
@ -608,6 +620,21 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
"""
self._sons.append(son)
def replace_son(self, ori_son: "Node", new_son: "Node") -> None:
"""Replace a son node. Do nothing if the node to replace is not a son
Args:
ori_son: son to replace
new_son: son to replace with
"""
for i, s in enumerate(self._sons):
if s.node_id == ori_son.node_id:
idx = i
break
else:
return
self._sons[idx] = new_son
def set_sons(self, sons: List["Node"]) -> None:
"""Set the son nodes
@ -663,20 +690,20 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._irs_ssa
@irs_ssa.setter
def irs_ssa(self, irs):
def irs_ssa(self, irs: List[Operation]) -> None:
self._irs_ssa = irs
def add_ssa_ir(self, ir: Operation) -> None:
"""
Use to place phi operation
"""
ir.set_node(self)
ir.set_node(self) # type: ignore
self._irs_ssa.append(ir)
def slithir_generation(self) -> None:
if self.expression:
expression = self.expression
self._irs = convert_expression(expression, self)
self._irs = convert_expression(expression, self) # type:ignore
self._find_read_write_call()
@ -713,7 +740,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._dominators
@dominators.setter
def dominators(self, dom: Set["Node"]):
def dominators(self, dom: Set["Node"]) -> None:
self._dominators = dom
@property
@ -725,7 +752,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._immediate_dominator
@immediate_dominator.setter
def immediate_dominator(self, idom: "Node"):
def immediate_dominator(self, idom: "Node") -> None:
self._immediate_dominator = idom
@property
@ -737,7 +764,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._dominance_frontier
@dominance_frontier.setter
def dominance_frontier(self, doms: Set["Node"]):
def dominance_frontier(self, doms: Set["Node"]) -> None:
"""
Returns:
set(Node)
@ -789,6 +816,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
def add_phi_origin_local_variable(self, variable: LocalVariable, node: "Node") -> None:
if variable.name not in self._phi_origins_local_variables:
assert variable.name
self._phi_origins_local_variables[variable.name] = (variable, set())
(v, nodes) = self._phi_origins_local_variables[variable.name]
assert v == variable
@ -827,7 +855,8 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
if isinstance(ir, OperationWithLValue):
var = ir.lvalue
if var and self._is_valid_slithir_var(var):
self._slithir_vars.add(var)
# The type is checked by is_valid_slithir_var
self._slithir_vars.add(var) # type: ignore
if not isinstance(ir, (Phi, Index, Member)):
self._vars_read += [v for v in ir.read if self._is_non_slithir_var(v)]
@ -835,8 +864,9 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
if isinstance(var, ReferenceVariable):
self._vars_read.append(var.points_to_origin)
elif isinstance(ir, (Member, Index)):
# TODO investigate types for member variable left
var = ir.variable_left if isinstance(ir, Member) else ir.variable_right
if self._is_non_slithir_var(var):
if var and self._is_non_slithir_var(var):
self._vars_read.append(var)
if isinstance(var, ReferenceVariable):
origin = var.points_to_origin
@ -860,14 +890,21 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._internal_calls.append(ir.function)
if isinstance(ir, LowLevelCall):
assert isinstance(ir.destination, (Variable, SolidityVariable))
self._low_level_calls.append((ir.destination, ir.function_name.value))
self._low_level_calls.append((ir.destination, str(ir.function_name.value)))
elif isinstance(ir, HighLevelCall) and not isinstance(ir, LibraryCall):
# Todo investigate this if condition
# It does seem right to compare against a contract
# This might need a refactoring
if isinstance(ir.destination.type, Contract):
self._high_level_calls.append((ir.destination.type, ir.function))
elif ir.destination == SolidityVariable("this"):
self._high_level_calls.append((self.function.contract, ir.function))
func = self.function
# Can't use this in a top level function
assert isinstance(func, FunctionContract)
self._high_level_calls.append((func.contract, ir.function))
else:
try:
# Todo this part needs more tests and documentation
self._high_level_calls.append((ir.destination.type.type, ir.function))
except AttributeError as error:
# pylint: disable=raise-missing-from
@ -883,7 +920,9 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._vars_read = list(set(self._vars_read))
self._state_vars_read = [v for v in self._vars_read if isinstance(v, StateVariable)]
self._local_vars_read = [v for v in self._vars_read if isinstance(v, LocalVariable)]
self._solidity_vars_read = [v for v in self._vars_read if isinstance(v, SolidityVariable)]
self._solidity_vars_read = [
v_ for v_ in self._vars_read if isinstance(v_, SolidityVariable)
]
self._vars_written = list(set(self._vars_written))
self._state_vars_written = [v for v in self._vars_written if isinstance(v, StateVariable)]
self._local_vars_written = [v for v in self._vars_written if isinstance(v, LocalVariable)]
@ -895,12 +934,15 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
@staticmethod
def _convert_ssa(v: Variable) -> Optional[Union[StateVariable, LocalVariable]]:
non_ssa_var: Optional[Union[StateVariable, LocalVariable]]
if isinstance(v, StateIRVariable):
contract = v.contract
assert v.name
non_ssa_var = contract.get_state_variable_from_name(v.name)
return non_ssa_var
assert isinstance(v, LocalIRVariable)
function = v.function
assert v.name
non_ssa_var = function.get_local_variable_from_name(v.name)
return non_ssa_var
@ -921,10 +963,11 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._ssa_vars_read.append(origin)
elif isinstance(ir, (Member, Index)):
if isinstance(ir.variable_right, (StateIRVariable, LocalIRVariable)):
self._ssa_vars_read.append(ir.variable_right)
if isinstance(ir.variable_right, ReferenceVariable):
origin = ir.variable_right.points_to_origin
variable_right: RVALUE = ir.variable_right
if isinstance(variable_right, (StateIRVariable, LocalIRVariable)):
self._ssa_vars_read.append(variable_right)
if isinstance(variable_right, ReferenceVariable):
origin = variable_right.points_to_origin
if isinstance(origin, (StateIRVariable, LocalIRVariable)):
self._ssa_vars_read.append(origin)
@ -944,20 +987,20 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._ssa_local_vars_read = [v for v in self._ssa_vars_read if isinstance(v, LocalVariable)]
self._ssa_vars_written = list(set(self._ssa_vars_written))
self._ssa_state_vars_written = [
v for v in self._ssa_vars_written if isinstance(v, StateVariable)
v for v in self._ssa_vars_written if v and isinstance(v, StateIRVariable)
]
self._ssa_local_vars_written = [
v for v in self._ssa_vars_written if isinstance(v, LocalVariable)
v for v in self._ssa_vars_written if v and isinstance(v, LocalIRVariable)
]
vars_read = [self._convert_ssa(x) for x in self._ssa_vars_read]
vars_written = [self._convert_ssa(x) for x in self._ssa_vars_written]
self._vars_read += [v for v in vars_read if v not in self._vars_read]
self._vars_read += [v_ for v_ in vars_read if v_ and v_ not in self._vars_read]
self._state_vars_read = [v for v in self._vars_read if isinstance(v, StateVariable)]
self._local_vars_read = [v for v in self._vars_read if isinstance(v, LocalVariable)]
self._vars_written += [v for v in vars_written if v not in self._vars_written]
self._vars_written += [v_ for v_ in vars_written if v_ and v_ not in self._vars_written]
self._state_vars_written = [v for v in self._vars_written if isinstance(v, StateVariable)]
self._local_vars_written = [v for v in self._vars_written if isinstance(v, LocalVariable)]
@ -974,7 +1017,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
additional_info += " " + str(self.expression)
elif self.variable_declaration:
additional_info += " " + str(self.variable_declaration)
txt = self._node_type.value + additional_info
txt = str(self._node_type.value) + additional_info
return txt

@ -1,19 +0,0 @@
from typing import TYPE_CHECKING
from slither.core.source_mapping.source_mapping import SourceMapping
if TYPE_CHECKING:
from slither.core.declarations import Contract
class ChildContract(SourceMapping):
def __init__(self) -> None:
super().__init__()
self._contract = None
def set_contract(self, contract: "Contract") -> None:
self._contract = contract
@property
def contract(self) -> "Contract":
return self._contract

@ -1,17 +0,0 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from slither.core.declarations import Event
class ChildEvent:
def __init__(self) -> None:
super().__init__()
self._event = None
def set_event(self, event: "Event"):
self._event = event
@property
def event(self) -> "Event":
return self._event

@ -1,18 +0,0 @@
from typing import TYPE_CHECKING, Union
if TYPE_CHECKING:
from slither.core.expressions.expression import Expression
from slither.slithir.operations import Operation
class ChildExpression:
def __init__(self) -> None:
super().__init__()
self._expression = None
def set_expression(self, expression: Union["Expression", "Operation"]) -> None:
self._expression = expression
@property
def expression(self) -> Union["Expression", "Operation"]:
return self._expression

@ -1,17 +0,0 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from slither.core.declarations import Function
class ChildFunction:
def __init__(self) -> None:
super().__init__()
self._function = None
def set_function(self, function: "Function") -> None:
self._function = function
@property
def function(self) -> "Function":
return self._function

@ -1,17 +0,0 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from slither.core.declarations import Contract
class ChildInheritance:
def __init__(self) -> None:
super().__init__()
self._contract_declarer = None
def set_contract_declarer(self, contract: "Contract") -> None:
self._contract_declarer = contract
@property
def contract_declarer(self) -> "Contract":
return self._contract_declarer

@ -1,31 +0,0 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.cfg.node import Node
from slither.core.declarations import Function, Contract
class ChildNode:
def __init__(self) -> None:
super().__init__()
self._node = None
def set_node(self, node: "Node") -> None:
self._node = node
@property
def node(self) -> "Node":
return self._node
@property
def function(self) -> "Function":
return self.node.function
@property
def contract(self) -> "Contract":
return self.node.function.contract
@property
def compilation_unit(self) -> "SlitherCompilationUnit":
return self.node.compilation_unit

@ -1,17 +0,0 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from slither.core.declarations import Structure
class ChildStructure:
def __init__(self) -> None:
super().__init__()
self._structure = None
def set_structure(self, structure: "Structure") -> None:
self._structure = structure
@property
def structure(self) -> "Structure":
return self._structure

@ -57,7 +57,7 @@ class SlitherCompilationUnit(Context):
self._storage_layouts: Dict[str, Dict[str, Tuple[int, int]]] = {}
self._contract_with_missing_inheritance = set()
self._contract_with_missing_inheritance: Set[Contract] = set()
self._source_units: Dict[int, str] = {}
@ -88,7 +88,8 @@ class SlitherCompilationUnit(Context):
@property
def solc_version(self) -> str:
return self._crytic_compile_compilation_unit.compiler_version.version
# TODO: make version a non optional argument of compiler version in cc
return self._crytic_compile_compilation_unit.compiler_version.version # type:ignore
@property
def crytic_compile_compilation_unit(self) -> CompilationUnit:
@ -127,7 +128,7 @@ class SlitherCompilationUnit(Context):
"""list(Contract): List of contracts that are derived and not inherited."""
inheritances = [x.inheritance for x in self.contracts]
inheritance = [item for sublist in inheritances for item in sublist]
return [c for c in self.contracts if c not in inheritance and not c.is_top_level]
return [c for c in self.contracts if c not in inheritance]
def get_contract_from_name(self, contract_name: Union[str, Constant]) -> List[Contract]:
"""
@ -162,13 +163,14 @@ class SlitherCompilationUnit(Context):
@property
def functions_and_modifiers(self) -> List[Function]:
return self.functions + self.modifiers
return self.functions + list(self.modifiers)
def propagate_function_calls(self) -> None:
for f in self.functions_and_modifiers:
for node in f.nodes:
for ir in node.irs_ssa:
if isinstance(ir, InternalCall):
assert ir.function
ir.function.add_reachable_from_node(node, ir)
# endregion
@ -181,8 +183,8 @@ class SlitherCompilationUnit(Context):
@property
def state_variables(self) -> List[StateVariable]:
if self._all_state_variables is None:
state_variables = [c.state_variables for c in self.contracts]
state_variables = [item for sublist in state_variables for item in sublist]
state_variabless = [c.state_variables for c in self.contracts]
state_variables = [item for sublist in state_variabless for item in sublist]
self._all_state_variables = set(state_variables)
return list(self._all_state_variables)
@ -229,7 +231,7 @@ class SlitherCompilationUnit(Context):
###################################################################################
@property
def contracts_with_missing_inheritance(self) -> Set:
def contracts_with_missing_inheritance(self) -> Set[Contract]:
return self._contract_with_missing_inheritance
# endregion
@ -266,6 +268,7 @@ class SlitherCompilationUnit(Context):
if var.is_constant or var.is_immutable:
continue
assert var.type
size, new_slot = var.type.storage_size
if new_slot:
@ -285,7 +288,7 @@ class SlitherCompilationUnit(Context):
else:
offset += size
def storage_layout_of(self, contract, var) -> Tuple[int, int]:
def storage_layout_of(self, contract: Contract, var: StateVariable) -> Tuple[int, int]:
return self._storage_layouts[contract.name][var.canonical_name]
# endregion

@ -49,6 +49,9 @@ 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
"""
@ -80,21 +83,23 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._custom_errors: Dict[str, "CustomErrorContract"] = {}
# The only str is "*"
self._using_for: Dict[Union[str, Type], List[Type]] = {}
self._using_for_complete: Dict[Union[str, Type], List[Type]] = None
self._using_for: Dict[USING_FOR_KEY, USING_FOR_ITEM] = {}
self._using_for_complete: Optional[Dict[USING_FOR_KEY, USING_FOR_ITEM]] = None
self._kind: Optional[str] = None
self._is_interface: bool = False
self._is_library: bool = False
self._is_fully_implemented: bool = False
self._signatures: Optional[List[str]] = None
self._signatures_declared: Optional[List[str]] = None
self._fallback_function: Optional["FunctionContract"] = None
self._receive_function: Optional["FunctionContract"] = None
self._is_upgradeable: Optional[bool] = None
self._is_upgradeable_proxy: Optional[bool] = None
self._upgradeable_version: Optional[str] = None
self.is_top_level = False # heavily used, so no @property
self._initial_state_variables: List["StateVariable"] = [] # ssa
self._is_incorrectly_parsed: bool = False
@ -125,7 +130,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return self._name
@name.setter
def name(self, name: str):
def name(self, name: str) -> None:
self._name = name
@property
@ -135,7 +140,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return self._id
@id.setter
def id(self, new_id):
def id(self, new_id: int) -> None:
"""Unique id."""
self._id = new_id
@ -148,7 +153,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return self._kind
@contract_kind.setter
def contract_kind(self, kind):
def contract_kind(self, kind: str) -> None:
self._kind = kind
@property
@ -156,7 +161,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return self._is_interface
@is_interface.setter
def is_interface(self, is_interface: bool):
def is_interface(self, is_interface: bool) -> None:
self._is_interface = is_interface
@property
@ -164,7 +169,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return self._is_library
@is_library.setter
def is_library(self, is_library: bool):
def is_library(self, is_library: bool) -> None:
self._is_library = is_library
@property
@ -192,6 +197,14 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def comments(self, comments: str):
self._comments = comments
@property
def is_fully_implemented(self) -> bool:
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
# endregion
###################################################################################
###################################################################################
@ -293,16 +306,18 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
@property
def using_for(self) -> Dict[Union[str, Type], List[Type]]:
def using_for(self) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
return self._using_for
@property
def using_for_complete(self) -> Dict[Union[str, Type], List[Type]]:
def using_for_complete(self) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
"""
Dict[Union[str, Type], List[Type]]: Dict of merged local using for directive with top level directive
"""
def _merge_using_for(uf1, uf2):
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:
@ -482,7 +497,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
)
@property
def constructors(self) -> List["Function"]:
def constructors(self) -> List["FunctionContract"]:
"""
Return the list of constructors (including inherited)
"""
@ -551,14 +566,14 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
return list(self._functions.values())
def available_functions_as_dict(self) -> Dict[str, "FunctionContract"]:
def available_functions_as_dict(self) -> Dict[str, "Function"]:
if self._available_functions_as_dict is None:
self._available_functions_as_dict = {
f.full_name: f for f in self._functions.values() if not f.is_shadowed
}
return self._available_functions_as_dict
def add_function(self, func: "FunctionContract"):
def add_function(self, func: "FunctionContract") -> None:
self._functions[func.canonical_name] = func
def set_functions(self, functions: Dict[str, "FunctionContract"]) -> None:
@ -649,6 +664,24 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
return self.functions_declared + self.modifiers_declared # type: ignore
@property
def fallback_function(self) -> Optional["FunctionContract"]:
if self._fallback_function is None:
for f in self.functions:
if f.is_fallback:
self._fallback_function = f
break
return self._fallback_function
@property
def receive_function(self) -> Optional["FunctionContract"]:
if self._receive_function is None:
for f in self.functions:
if f.is_receive:
self._receive_function = f
break
return self._receive_function
def available_elements_from_inheritances(
self,
elements: Dict[str, "Function"],
@ -726,7 +759,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
list(Contract): Return the list of contracts derived from self
"""
candidates = self.compilation_unit.contracts
return [c for c in candidates if self in c.inheritance]
return [c for c in candidates if self in c.inheritance] # type: ignore
# endregion
###################################################################################
@ -882,7 +915,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
return next((e for e in self.enums if e.name == enum_name), None)
def get_enum_from_canonical_name(self, enum_name) -> Optional["Enum"]:
def get_enum_from_canonical_name(self, enum_name: str) -> Optional["Enum"]:
"""
Return an enum from a canonical name
Args:
@ -983,7 +1016,9 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
###################################################################################
def get_summary(self, include_shadowed=True) -> Tuple[str, List[str], List[str], List, List]:
def get_summary(
self, include_shadowed: bool = True
) -> Tuple[str, List[str], List[str], List, List]:
"""Return the function summary
:param include_shadowed: boolean to indicate if shadowed functions should be included (default True)
@ -1236,7 +1271,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
@property
def is_test(self) -> bool:
return is_test_contract(self) or self.is_truffle_migration
return is_test_contract(self) or self.is_truffle_migration # type: ignore
# endregion
###################################################################################
@ -1246,7 +1281,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
def update_read_write_using_ssa(self) -> None:
for function in self.functions + self.modifiers:
for function in self.functions + list(self.modifiers):
function.update_read_write_using_ssa()
# endregion
@ -1281,7 +1316,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return self._is_upgradeable
@is_upgradeable.setter
def is_upgradeable(self, upgradeable: bool):
def is_upgradeable(self, upgradeable: bool) -> None:
self._is_upgradeable = upgradeable
@property
@ -1310,7 +1345,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return self._is_upgradeable_proxy
@is_upgradeable_proxy.setter
def is_upgradeable_proxy(self, upgradeable_proxy: bool):
def is_upgradeable_proxy(self, upgradeable_proxy: bool) -> None:
self._is_upgradeable_proxy = upgradeable_proxy
@property
@ -1318,7 +1353,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return self._upgradeable_version
@upgradeable_version.setter
def upgradeable_version(self, version_name: str):
def upgradeable_version(self, version_name: str) -> None:
self._upgradeable_version = version_name
# endregion
@ -1337,7 +1372,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return self._is_incorrectly_parsed
@is_incorrectly_constructed.setter
def is_incorrectly_constructed(self, incorrect: bool):
def is_incorrectly_constructed(self, incorrect: bool) -> None:
self._is_incorrectly_parsed = incorrect
def add_constructor_variables(self) -> None:
@ -1349,8 +1384,8 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
constructor_variable = FunctionContract(self.compilation_unit)
constructor_variable.set_function_type(FunctionType.CONSTRUCTOR_VARIABLES)
constructor_variable.set_contract(self)
constructor_variable.set_contract_declarer(self)
constructor_variable.set_contract(self) # type: ignore
constructor_variable.set_contract_declarer(self) # type: ignore
constructor_variable.set_visibility("internal")
# For now, source mapping of the constructor variable is the whole contract
# Could be improved with a targeted source mapping
@ -1381,8 +1416,8 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
constructor_variable.set_function_type(
FunctionType.CONSTRUCTOR_CONSTANT_VARIABLES
)
constructor_variable.set_contract(self)
constructor_variable.set_contract_declarer(self)
constructor_variable.set_contract(self) # type: ignore
constructor_variable.set_contract_declarer(self) # type: ignore
constructor_variable.set_visibility("internal")
# For now, source mapping of the constructor variable is the whole contract
# Could be improved with a targeted source mapping
@ -1463,22 +1498,23 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
all_ssa_state_variables_instances[v.canonical_name] = new_var
self._initial_state_variables.append(new_var)
for func in self.functions + self.modifiers:
for func in self.functions + list(self.modifiers):
func.generate_slithir_ssa(all_ssa_state_variables_instances)
def fix_phi(self) -> None:
last_state_variables_instances = {}
initial_state_variables_instances = {}
last_state_variables_instances: Dict[str, List["StateVariable"]] = {}
initial_state_variables_instances: Dict[str, "StateVariable"] = {}
for v in self._initial_state_variables:
last_state_variables_instances[v.canonical_name] = []
initial_state_variables_instances[v.canonical_name] = v
for func in self.functions + self.modifiers:
for func in self.functions + list(self.modifiers):
result = func.get_last_ssa_state_variables_instances()
for variable_name, instances in result.items():
last_state_variables_instances[variable_name] += instances
# TODO: investigate the next operation
last_state_variables_instances[variable_name] += list(instances)
for func in self.functions + self.modifiers:
for func in self.functions + list(self.modifiers):
func.fix_phi(last_state_variables_instances, initial_state_variables_instances)
# endregion
@ -1488,7 +1524,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
###################################################################################
def __eq__(self, other: SourceMapping) -> bool:
def __eq__(self, other: Any) -> bool:
if isinstance(other, str):
return other == self.name
return NotImplemented
@ -1502,6 +1538,6 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return self.name
def __hash__(self) -> int:
return self._id
return self._id # type:ignore
# endregion

@ -0,0 +1,29 @@
from typing import TYPE_CHECKING, Optional
from slither.core.source_mapping.source_mapping import SourceMapping
if TYPE_CHECKING:
from slither.core.declarations import Contract
class ContractLevel(SourceMapping):
"""
This class is used to represent objects that are at the contract level
The opposite is TopLevel
"""
def __init__(self) -> None:
super().__init__()
# TODO remove all the setters for the child objects
# And make it a constructor arguement
# This will remove the optional
self._contract: Optional["Contract"] = None
def set_contract(self, contract: "Contract") -> None:
self._contract = contract
@property
def contract(self) -> "Contract":
assert self._contract
return self._contract

@ -1,4 +1,4 @@
from typing import List, TYPE_CHECKING, Optional, Type, Union
from typing import List, TYPE_CHECKING, Optional, Type
from slither.core.solidity_types import UserDefinedType
from slither.core.source_mapping.source_mapping import SourceMapping
@ -42,7 +42,7 @@ class CustomError(SourceMapping):
###################################################################################
@staticmethod
def _convert_type_for_solidity_signature(t: Optional[Union[Type, List[Type]]]) -> str:
def _convert_type_for_solidity_signature(t: Optional[Type]) -> str:
# pylint: disable=import-outside-toplevel
from slither.core.declarations import Contract
@ -51,7 +51,7 @@ class CustomError(SourceMapping):
return str(t)
@property
def solidity_signature(self) -> Optional[str]:
def solidity_signature(self) -> str:
"""
Return a signature following the Solidity Standard
Contract and converted into address
@ -63,7 +63,7 @@ class CustomError(SourceMapping):
# (set_solidity_sig was not called before find_variable)
if self._solidity_signature is None:
raise ValueError("Custom Error not yet built")
return self._solidity_signature
return self._solidity_signature # type: ignore
def set_solidity_sig(self) -> None:
"""
@ -72,7 +72,7 @@ class CustomError(SourceMapping):
Returns:
"""
parameters = [x.type for x in self.parameters]
parameters = [x.type for x in self.parameters if x.type]
self._full_name = self.name + "(" + ",".join(map(str, parameters)) + ")"
solidity_parameters = map(self._convert_type_for_solidity_signature, parameters)
self._solidity_signature = self.name + "(" + ",".join(solidity_parameters) + ")"

@ -1,9 +1,15 @@
from slither.core.children.child_contract import ChildContract
from typing import TYPE_CHECKING
from slither.core.declarations.contract_level import ContractLevel
from slither.core.declarations.custom_error import CustomError
if TYPE_CHECKING:
from slither.core.declarations import Contract
class CustomErrorContract(CustomError, ChildContract):
def is_declared_by(self, contract):
class CustomErrorContract(CustomError, ContractLevel):
def is_declared_by(self, contract: "Contract") -> bool:
"""
Check if the element is declared by the contract
:param contract:

@ -9,6 +9,6 @@ if TYPE_CHECKING:
class CustomErrorTopLevel(CustomError, TopLevel):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope"):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope") -> None:
super().__init__(compilation_unit)
self.file_scope: "FileScope" = scope

@ -1,13 +1,13 @@
from typing import TYPE_CHECKING
from slither.core.children.child_contract import ChildContract
from slither.core.declarations.contract_level import ContractLevel
from slither.core.declarations import Enum
if TYPE_CHECKING:
from slither.core.declarations import Contract
class EnumContract(Enum, ChildContract):
class EnumContract(Enum, ContractLevel):
def is_declared_by(self, contract: "Contract") -> bool:
"""
Check if the element is declared by the contract

@ -1,6 +1,6 @@
from typing import List, Tuple, TYPE_CHECKING
from slither.core.children.child_contract import ChildContract
from slither.core.declarations.contract_level import ContractLevel
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.event_variable import EventVariable
@ -8,7 +8,7 @@ if TYPE_CHECKING:
from slither.core.declarations import Contract
class Event(ChildContract, SourceMapping):
class Event(ContractLevel, SourceMapping):
def __init__(self) -> None:
super().__init__()
self._name = None

@ -47,7 +47,6 @@ if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.scope.scope import FileScope
from slither.slithir.variables.state_variable import StateIRVariable
from slither.core.declarations.function_contract import FunctionContract
LOGGER = logging.getLogger("Function")
ReacheableNode = namedtuple("ReacheableNode", ["node", "ir"])
@ -298,7 +297,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
def contains_assembly(self, c: bool):
self._contains_assembly = c
def can_reenter(self, callstack: Optional[List["FunctionContract"]] = None) -> bool:
def can_reenter(self, callstack: Optional[List[Union["Function", "Variable"]]] = None) -> bool:
"""
Check if the function can re-enter
Follow internal calls.
@ -1720,8 +1719,8 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
def fix_phi(
self,
last_state_variables_instances: Dict[str, List["StateIRVariable"]],
initial_state_variables_instances: Dict[str, "StateIRVariable"],
last_state_variables_instances: Dict[str, List["StateVariable"]],
initial_state_variables_instances: Dict[str, "StateVariable"],
) -> None:
from slither.slithir.operations import InternalCall, PhiCallback
from slither.slithir.variables import Constant, StateIRVariable

@ -1,10 +1,9 @@
"""
Function module
"""
from typing import Dict, TYPE_CHECKING, List, Tuple
from typing import Dict, TYPE_CHECKING, List, Tuple, Optional
from slither.core.children.child_contract import ChildContract
from slither.core.children.child_inheritance import ChildInheritance
from slither.core.declarations.contract_level import ContractLevel
from slither.core.declarations import Function
from slither.utils.code_complexity import compute_cyclomatic_complexity
@ -15,9 +14,31 @@ if TYPE_CHECKING:
from slither.core.declarations import Contract
from slither.core.scope.scope import FileScope
from slither.slithir.variables.state_variable import StateIRVariable
from slither.core.compilation_unit import SlitherCompilationUnit
class FunctionContract(Function, ChildContract, ChildInheritance):
class FunctionContract(Function, ContractLevel):
def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None:
super().__init__(compilation_unit)
self._contract_declarer: Optional["Contract"] = None
def set_contract_declarer(self, contract: "Contract") -> None:
self._contract_declarer = contract
@property
def contract_declarer(self) -> "Contract":
"""
Return the contract where this function was declared. Only functions have both a contract, and contract_declarer
This is because we need to have separate representation of the function depending of the contract's context
For example a function calling super.f() will generate different IR depending on the current contract's inheritance
Returns:
The contract where this function was declared
"""
assert self._contract_declarer
return self._contract_declarer
@property
def canonical_name(self) -> str:
"""

@ -82,7 +82,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
}
def solidity_function_signature(name):
def solidity_function_signature(name: str) -> str:
"""
Return the function signature (containing the return value)
It is useful if a solidity function is used as a pointer
@ -106,7 +106,7 @@ class SolidityVariable(SourceMapping):
assert name in SOLIDITY_VARIABLES or name.endswith(("_slot", "_offset"))
@property
def state_variable(self):
def state_variable(self) -> str:
if self._name.endswith("_slot"):
return self._name[:-5]
if self._name.endswith("_offset"):
@ -125,7 +125,7 @@ class SolidityVariable(SourceMapping):
def __str__(self) -> str:
return self._name
def __eq__(self, other: SourceMapping) -> bool:
def __eq__(self, other: Any) -> bool:
return self.__class__ == other.__class__ and self.name == other.name
def __hash__(self) -> int:
@ -182,13 +182,13 @@ class SolidityFunction(SourceMapping):
return self._return_type
@return_type.setter
def return_type(self, r: List[Union[TypeInformation, ElementaryType]]):
def return_type(self, r: List[Union[TypeInformation, ElementaryType]]) -> None:
self._return_type = r
def __str__(self) -> str:
return self._name
def __eq__(self, other: "SolidityFunction") -> bool:
def __eq__(self, other: Any) -> bool:
return self.__class__ == other.__class__ and self.name == other.name
def __hash__(self) -> int:
@ -201,7 +201,7 @@ class SolidityCustomRevert(SolidityFunction):
self._custom_error = custom_error
self._return_type: List[Union[TypeInformation, ElementaryType]] = []
def __eq__(self, other: Union["SolidityCustomRevert", SolidityFunction]) -> bool:
def __eq__(self, other: Any) -> bool:
return (
self.__class__ == other.__class__
and self.name == other.name

@ -1,8 +1,8 @@
from slither.core.children.child_contract import ChildContract
from slither.core.declarations.contract_level import ContractLevel
from slither.core.declarations import Structure
class StructureContract(Structure, ChildContract):
class StructureContract(Structure, ContractLevel):
def is_declared_by(self, contract):
"""
Check if the element is declared by the contract

@ -2,4 +2,8 @@ from slither.core.source_mapping.source_mapping import SourceMapping
class TopLevel(SourceMapping):
pass
"""
This class is used to represent objects that are at the top level
The opposite is ContractLevel
"""

@ -1,5 +1,6 @@
from typing import TYPE_CHECKING, List, Dict, Union
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
@ -14,5 +15,5 @@ class UsingForTopLevel(TopLevel):
self.file_scope: "FileScope" = scope
@property
def using_for(self) -> Dict[Union[str, Type], List[Type]]:
def using_for(self) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
return self._using_for

@ -95,4 +95,5 @@ def compute_dominance_frontier(nodes: List["Node"]) -> None:
runner.dominance_frontier = runner.dominance_frontier.union({node})
while runner != node.immediate_dominator:
runner.dominance_frontier = runner.dominance_frontier.union({node})
assert runner.immediate_dominator
runner = runner.immediate_dominator

@ -2,7 +2,6 @@ import logging
from enum import Enum
from typing import Optional, TYPE_CHECKING, List
from slither.core.expressions.expression_typed import ExpressionTyped
from slither.core.expressions.expression import Expression
from slither.core.exceptions import SlitherCoreError
@ -78,7 +77,7 @@ class AssignmentOperationType(Enum):
raise SlitherCoreError(f"str: Unknown operation type {self})")
class AssignmentOperation(ExpressionTyped):
class AssignmentOperation(Expression):
def __init__(
self,
left_expression: Expression,
@ -91,7 +90,7 @@ class AssignmentOperation(ExpressionTyped):
super().__init__()
left_expression.set_lvalue()
self._expressions = [left_expression, right_expression]
self._type: Optional["AssignmentOperationType"] = expression_type
self._type: AssignmentOperationType = expression_type
self._expression_return_type: Optional["Type"] = expression_return_type
@property

@ -2,7 +2,6 @@ import logging
from enum import Enum
from typing import List
from slither.core.expressions.expression_typed import ExpressionTyped
from slither.core.expressions.expression import Expression
from slither.core.exceptions import SlitherCoreError
@ -148,7 +147,7 @@ class BinaryOperationType(Enum):
raise SlitherCoreError(f"str: Unknown operation type {self})")
class BinaryOperation(ExpressionTyped):
class BinaryOperation(Expression):
def __init__(
self,
left_expression: Expression,

@ -22,7 +22,7 @@ class CallExpression(Expression): # pylint: disable=too-many-instance-attribute
return self._value
@call_value.setter
def call_value(self, v):
def call_value(self, v: Optional[Expression]) -> None:
self._value = v
@property
@ -30,15 +30,15 @@ class CallExpression(Expression): # pylint: disable=too-many-instance-attribute
return self._gas
@call_gas.setter
def call_gas(self, gas):
def call_gas(self, gas: Optional[Expression]) -> None:
self._gas = gas
@property
def call_salt(self):
def call_salt(self) -> Optional[Expression]:
return self._salt
@call_salt.setter
def call_salt(self, salt):
def call_salt(self, salt: Optional[Expression]) -> None:
self._salt = salt
@property

@ -42,7 +42,7 @@ class ConditionalExpression(Expression):
def then_expression(self) -> Expression:
return self._then_expression
def __str__(self):
def __str__(self) -> str:
return (
"if "
+ str(self._if_expression)

@ -1,20 +0,0 @@
from typing import Optional, TYPE_CHECKING
from slither.core.expressions.expression import Expression
if TYPE_CHECKING:
from slither.core.solidity_types.type import Type
class ExpressionTyped(Expression): # pylint: disable=too-few-public-methods
def __init__(self) -> None:
super().__init__()
self._type: Optional["Type"] = None
@property
def type(self) -> Optional["Type"]:
return self._type
@type.setter
def type(self, new_type: "Type"):
self._type = new_type

@ -1,18 +1,80 @@
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional, Union
from slither.core.declarations.contract_level import ContractLevel
from slither.core.declarations.top_level import TopLevel
from slither.core.expressions.expression import Expression
from slither.core.variables.variable import Variable
from slither.core.expressions.expression_typed import ExpressionTyped
if TYPE_CHECKING:
from slither.core.variables.variable import Variable
from slither.core.solidity_types.type import Type
from slither.core.declarations import Contract, SolidityVariable, SolidityFunction
from slither.solc_parsing.yul.evm_functions import YulBuiltin
class Identifier(ExpressionTyped):
def __init__(self, value) -> None:
class Identifier(Expression):
def __init__(
self,
value: Union[
Variable,
"TopLevel",
"ContractLevel",
"Contract",
"SolidityVariable",
"SolidityFunction",
"YulBuiltin",
],
) -> None:
super().__init__()
self._value: "Variable" = value
# pylint: disable=import-outside-toplevel
from slither.core.declarations import Contract, SolidityVariable, SolidityFunction
from slither.solc_parsing.yul.evm_functions import YulBuiltin
assert isinstance(
value,
(
Variable,
TopLevel,
ContractLevel,
Contract,
SolidityVariable,
SolidityFunction,
YulBuiltin,
),
)
self._value: Union[
Variable,
"TopLevel",
"ContractLevel",
"Contract",
"SolidityVariable",
"SolidityFunction",
"YulBuiltin",
] = value
self._type: Optional["Type"] = None
@property
def type(self) -> Optional["Type"]:
return self._type
@type.setter
def type(self, new_type: "Type") -> None:
self._type = new_type
@property
def value(self) -> "Variable":
def value(
self,
) -> Union[
Variable,
"TopLevel",
"ContractLevel",
"Contract",
"SolidityVariable",
"SolidityFunction",
"YulBuiltin",
]:
return self._value
def __str__(self) -> str:

@ -1,27 +1,18 @@
from typing import Union, List, TYPE_CHECKING
from typing import Union, List
from slither.core.expressions.expression_typed import ExpressionTyped
from slither.core.expressions.expression import Expression
from slither.core.expressions.identifier import Identifier
from slither.core.expressions.literal import Literal
if TYPE_CHECKING:
from slither.core.expressions.expression import Expression
from slither.core.solidity_types.type import Type
class IndexAccess(ExpressionTyped):
class IndexAccess(Expression):
def __init__(
self,
left_expression: Union["IndexAccess", Identifier],
right_expression: Union[Literal, Identifier],
index_type: str,
) -> None:
super().__init__()
self._expressions = [left_expression, right_expression]
# TODO type of undexAccess is not always a Type
# assert isinstance(index_type, Type)
self._type: "Type" = index_type
@property
def expressions(self) -> List["Expression"]:
@ -35,9 +26,5 @@ class IndexAccess(ExpressionTyped):
def expression_right(self) -> "Expression":
return self._expressions[1]
@property
def type(self) -> "Type":
return self._type
def __str__(self) -> str:
return str(self.expression_left) + "[" + str(self.expression_right) + "]"

@ -1,4 +1,4 @@
from typing import Optional, Union, TYPE_CHECKING
from typing import Optional, Union, TYPE_CHECKING, Any
from slither.core.expressions.expression import Expression
from slither.core.solidity_types.elementary_type import Fixed, Int, Ufixed, Uint
@ -47,7 +47,7 @@ class Literal(Expression):
# be sure to handle any character
return str(self._value)
def __eq__(self, other) -> bool:
def __eq__(self, other: Any) -> bool:
if not isinstance(other, Literal):
return False
return (self.value, self.subdenomination) == (other.value, other.subdenomination)

@ -1,10 +1,9 @@
from slither.core.expressions.expression import Expression
from slither.core.expressions.expression_typed import ExpressionTyped
from slither.core.solidity_types.type import Type
class MemberAccess(ExpressionTyped):
class MemberAccess(Expression):
def __init__(self, member_name: str, member_type: str, expression: Expression) -> None:
# assert isinstance(member_type, Type)
# TODO member_type is not always a Type

@ -1,6 +1,5 @@
from typing import Union, TYPE_CHECKING
from slither.core.expressions.expression_typed import ExpressionTyped
from slither.core.expressions.expression import Expression
from slither.core.solidity_types.type import Type
@ -14,7 +13,7 @@ if TYPE_CHECKING:
from slither.core.solidity_types.user_defined_type import UserDefinedType
class TypeConversion(ExpressionTyped):
class TypeConversion(Expression):
def __init__(
self,
expression: Union[
@ -28,6 +27,14 @@ class TypeConversion(ExpressionTyped):
self._expression: Expression = expression
self._type: Type = expression_type
@property
def type(self) -> Type:
return self._type
@type.setter
def type(self, new_type: Type) -> None:
self._type = new_type
@property
def expression(self) -> Expression:
return self._expression

@ -2,7 +2,6 @@ import logging
from typing import Union
from enum import Enum
from slither.core.expressions.expression_typed import ExpressionTyped
from slither.core.expressions.expression import Expression
from slither.core.exceptions import SlitherCoreError
from slither.core.expressions.identifier import Identifier
@ -91,7 +90,7 @@ class UnaryOperationType(Enum):
raise SlitherCoreError(f"is_prefix: Unknown operation type {operation_type}")
class UnaryOperation(ExpressionTyped):
class UnaryOperation(Expression):
def __init__(
self,
expression: Union[Literal, Identifier, IndexAccess, TupleExpression],

@ -13,7 +13,7 @@ from typing import Optional, Dict, List, Set, Union, Tuple
from crytic_compile import CryticCompile
from crytic_compile.utils.naming import Filename
from slither.core.children.child_contract import ChildContract
from slither.core.declarations.contract_level import ContractLevel
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.context.context import Context
from slither.core.declarations import Contract, FunctionContract
@ -206,7 +206,7 @@ class SlitherCore(Context):
isinstance(thing, FunctionContract)
and thing.contract_declarer == thing.contract
)
or (isinstance(thing, ChildContract) and not isinstance(thing, FunctionContract))
or (isinstance(thing, ContractLevel) and not isinstance(thing, FunctionContract))
):
self._offset_to_objects[definition.filename][offset].add(thing)
@ -224,7 +224,7 @@ class SlitherCore(Context):
and thing.contract_declarer == thing.contract
)
or (
isinstance(thing, ChildContract) and not isinstance(thing, FunctionContract)
isinstance(thing, ContractLevel) and not isinstance(thing, FunctionContract)
)
):
self._offset_to_objects[definition.filename][offset].add(thing)
@ -482,8 +482,8 @@ class SlitherCore(Context):
###################################################################################
@property
def crytic_compile(self) -> Optional[CryticCompile]:
return self._crytic_compile
def crytic_compile(self) -> CryticCompile:
return self._crytic_compile # type: ignore
# endregion
###################################################################################

@ -1,38 +1,37 @@
from typing import Union, Optional, Tuple, Any, TYPE_CHECKING
from slither.core.expressions.expression import Expression
from slither.core.expressions.literal import Literal
from slither.core.solidity_types.elementary_type import ElementaryType
from slither.core.solidity_types.type import Type
from slither.visitors.expression.constants_folding import ConstantFolding
from slither.core.expressions.literal import Literal
if TYPE_CHECKING:
from slither.core.expressions.binary_operation import BinaryOperation
from slither.core.expressions.identifier import Identifier
from slither.core.solidity_types.elementary_type import ElementaryType
from slither.core.solidity_types.function_type import FunctionType
from slither.core.solidity_types.type_alias import TypeAliasTopLevel
class ArrayType(Type):
def __init__(
self,
t: Union["TypeAliasTopLevel", "ArrayType", "FunctionType", "ElementaryType"],
t: Type,
length: Optional[Union["Identifier", Literal, "BinaryOperation", int]],
) -> None:
assert isinstance(t, Type)
if length:
if isinstance(length, int):
length = Literal(length, "uint256")
assert isinstance(length, Expression)
length = Literal(length, ElementaryType("uint256"))
super().__init__()
self._type: Type = t
assert length is None or isinstance(length, Expression)
self._length: Optional[Expression] = length
if length:
if not isinstance(length, Literal):
cf = ConstantFolding(length, "uint256")
length = cf.result()
self._length_value = length
self._length_value: Optional[Literal] = length
else:
self._length_value = None

@ -1,5 +1,5 @@
import itertools
from typing import Tuple
from typing import Tuple, Optional, Any
from slither.core.solidity_types.type import Type
@ -176,7 +176,7 @@ class ElementaryType(Type):
return self.type
@property
def size(self) -> int:
def size(self) -> Optional[int]:
"""
Return the size in bits
Return None if the size is not known
@ -219,7 +219,7 @@ class ElementaryType(Type):
def __str__(self) -> str:
return self._type
def __eq__(self, other) -> bool:
def __eq__(self, other: Any) -> bool:
if not isinstance(other, ElementaryType):
return False
return self.type == other.type

@ -1,4 +1,4 @@
from typing import Union, Tuple, TYPE_CHECKING
from typing import Union, Tuple, TYPE_CHECKING, Any
from slither.core.solidity_types.type import Type
@ -38,7 +38,7 @@ class MappingType(Type):
def __str__(self) -> str:
return f"mapping({str(self._from)} => {str(self._to)})"
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
if not isinstance(other, MappingType):
return False
return self.type_from == other.type_from and self.type_to == other.type_to

@ -1,7 +1,7 @@
from typing import TYPE_CHECKING, Tuple
from slither.core.children.child_contract import ChildContract
from slither.core.declarations.top_level import TopLevel
from slither.core.declarations.contract_level import ContractLevel
from slither.core.solidity_types import Type, ElementaryType
if TYPE_CHECKING:
@ -40,7 +40,7 @@ class TypeAlias(Type):
class TypeAliasTopLevel(TypeAlias, TopLevel):
def __init__(self, underlying_type: Type, name: str, scope: "FileScope") -> None:
def __init__(self, underlying_type: ElementaryType, name: str, scope: "FileScope") -> None:
super().__init__(underlying_type, name)
self.file_scope: "FileScope" = scope
@ -48,8 +48,8 @@ class TypeAliasTopLevel(TypeAlias, TopLevel):
return self.name
class TypeAliasContract(TypeAlias, ChildContract):
def __init__(self, underlying_type: Type, name: str, contract: "Contract") -> None:
class TypeAliasContract(TypeAlias, ContractLevel):
def __init__(self, underlying_type: ElementaryType, name: str, contract: "Contract") -> None:
super().__init__(underlying_type, name)
self._contract: "Contract" = contract

@ -1,4 +1,4 @@
from typing import Union, TYPE_CHECKING, Tuple
from typing import Union, TYPE_CHECKING, Tuple, Any
from slither.core.solidity_types import ElementaryType
from slither.core.solidity_types.type import Type
@ -40,10 +40,10 @@ class TypeInformation(Type):
def is_dynamic(self) -> bool:
raise NotImplementedError
def __str__(self):
def __str__(self) -> str:
return f"type({self.type.name})"
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
if not isinstance(other, TypeInformation):
return False
return self.type == other.type

@ -1,6 +1,6 @@
import re
from abc import ABCMeta
from typing import Dict, Union, List, Tuple, TYPE_CHECKING, Optional
from typing import Dict, Union, List, Tuple, TYPE_CHECKING, Optional, Any
from Crypto.Hash import SHA1
from crytic_compile.utils.naming import Filename
@ -98,10 +98,10 @@ class Source:
filename_short: str = self.filename.short if self.filename.short else ""
return f"{filename_short}{lines}"
def __hash__(self):
def __hash__(self) -> int:
return hash(str(self))
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
if not isinstance(other, type(self)):
return NotImplemented
return (

@ -1,8 +1,7 @@
from slither.core.variables.variable import Variable
from slither.core.children.child_event import ChildEvent
class EventVariable(ChildEvent, Variable):
class EventVariable(Variable):
def __init__(self) -> None:
super().__init__()
self._indexed = False
@ -16,5 +15,5 @@ class EventVariable(ChildEvent, Variable):
return self._indexed
@indexed.setter
def indexed(self, is_indexed: bool):
def indexed(self, is_indexed: bool) -> None:
self._indexed = is_indexed

@ -1,7 +1,6 @@
from typing import Optional
from typing import Optional, TYPE_CHECKING
from slither.core.variables.variable import Variable
from slither.core.children.child_function import ChildFunction
from slither.core.solidity_types.user_defined_type import UserDefinedType
from slither.core.solidity_types.array_type import ArrayType
from slither.core.solidity_types.mapping_type import MappingType
@ -9,11 +8,23 @@ from slither.core.solidity_types.elementary_type import ElementaryType
from slither.core.declarations.structure import Structure
if TYPE_CHECKING: # type: ignore
from slither.core.declarations import Function
class LocalVariable(ChildFunction, Variable):
class LocalVariable(Variable):
def __init__(self) -> None:
super().__init__()
self._location: Optional[str] = None
self._function: Optional["Function"] = None
def set_function(self, function: "Function") -> None:
self._function = function
@property
def function(self) -> "Function":
assert self._function
return self._function
def set_location(self, loc: str) -> None:
self._location = loc
@ -42,6 +53,8 @@ class LocalVariable(ChildFunction, Variable):
"""
if self.location == "memory":
return False
if self.location == "calldata":
return False
# Use by slithIR SSA
if self.location == "reference_to_storage":
return False

@ -1,6 +1,6 @@
from typing import Optional, TYPE_CHECKING
from slither.core.children.child_contract import ChildContract
from slither.core.declarations.contract_level import ContractLevel
from slither.core.variables.variable import Variable
if TYPE_CHECKING:
@ -8,7 +8,7 @@ if TYPE_CHECKING:
from slither.core.declarations import Contract
class StateVariable(ChildContract, Variable):
class StateVariable(ContractLevel, Variable):
def __init__(self) -> None:
super().__init__()
self._node_initialization: Optional["Node"] = None

@ -1,6 +1,19 @@
from typing import TYPE_CHECKING, Optional
from slither.core.variables.variable import Variable
from slither.core.children.child_structure import ChildStructure
class StructureVariable(ChildStructure, Variable):
pass
if TYPE_CHECKING:
from slither.core.declarations import Structure
class StructureVariable(Variable):
def __init__(self) -> None:
super().__init__()
self._structure: Optional["Structure"] = None
def set_structure(self, structure: "Structure") -> None:
self._structure = structure
@property
def structure(self) -> "Structure":
return self._structure

@ -55,7 +55,7 @@ class Variable(SourceMapping):
return self._initialized
@initialized.setter
def initialized(self, is_init: bool):
def initialized(self, is_init: bool) -> None:
self._initialized = is_init
@property
@ -73,23 +73,24 @@ class Variable(SourceMapping):
return self._name
@name.setter
def name(self, name):
def name(self, name: str) -> None:
self._name = name
@property
def type(self) -> Optional[Union[Type, List[Type]]]:
def type(self) -> Optional[Type]:
return self._type
@type.setter
def type(self, types: Union[Type, List[Type]]):
self._type = types
def type(self, new_type: Type) -> None:
assert isinstance(new_type, Type)
self._type = new_type
@property
def is_constant(self) -> bool:
return self._is_constant
@is_constant.setter
def is_constant(self, is_cst: bool):
def is_constant(self, is_cst: bool) -> None:
self._is_constant = is_cst
@property
@ -159,8 +160,8 @@ class Variable(SourceMapping):
return (
self.name,
[str(x) for x in export_nested_types_from_variable(self)],
[str(x) for x in export_return_type_from_variable(self)],
[str(x) for x in export_nested_types_from_variable(self)], # type: ignore
[str(x) for x in export_return_type_from_variable(self)], # type: ignore
)
@property
@ -178,4 +179,5 @@ class Variable(SourceMapping):
return f'{name}({",".join(parameters)})'
def __str__(self) -> str:
assert self._name
return self._name

@ -59,6 +59,8 @@ ALL_SOLC_VERSIONS_06 = make_solc_versions(6, 0, 12)
ALL_SOLC_VERSIONS_07 = make_solc_versions(7, 0, 6)
# No VERSIONS_08 as it is still in dev
DETECTOR_INFO = List[Union[str, SupportedOutput]]
class AbstractDetector(metaclass=abc.ABCMeta):
ARGUMENT = "" # run the detector with slither.py --ARGUMENT
@ -251,7 +253,7 @@ class AbstractDetector(metaclass=abc.ABCMeta):
def generate_result(
self,
info: Union[str, List[Union[str, SupportedOutput]]],
info: DETECTOR_INFO,
additional_fields: Optional[Dict] = None,
) -> Output:
output = Output(

@ -1,5 +1,9 @@
from typing import List
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import Binary, BinaryType
from slither.slithir.variables import Constant
from slither.core.declarations.function_contract import FunctionContract
@ -49,7 +53,12 @@ The shift statement will right-shift the constant 8 by `a` bits"""
BinaryType.RIGHT_SHIFT,
]:
if isinstance(ir.variable_left, Constant):
info = [f, " contains an incorrect shift operation: ", node, "\n"]
info: DETECTOR_INFO = [
f,
" contains an incorrect shift operation: ",
node,
"\n",
]
json = self.generate_result(info)
results.append(json)

@ -2,11 +2,14 @@
Module detecting constant functions
Recursively check the called functions
"""
from typing import List
from typing import List, Dict
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
ALL_SOLC_VERSIONS_04,
DETECTOR_INFO,
)
from slither.formatters.attributes.const_functions import custom_format
from slither.utils.output import Output
@ -73,7 +76,10 @@ All the calls to `get` revert, breaking Bob's smart contract execution."""
if f.contains_assembly:
attr = "view" if f.view else "pure"
info = [f, f" is declared {attr} but contains assembly code\n"]
info: DETECTOR_INFO = [
f,
f" is declared {attr} but contains assembly code\n",
]
res = self.generate_result(info, {"contains_assembly": True})
results.append(res)
@ -81,5 +87,5 @@ All the calls to `get` revert, breaking Bob's smart contract execution."""
return results
@staticmethod
def _format(comilation_unit, result):
def _format(comilation_unit: SlitherCompilationUnit, result: Dict) -> None:
custom_format(comilation_unit, result)

@ -2,11 +2,14 @@
Module detecting constant functions
Recursively check the called functions
"""
from typing import List
from typing import List, Dict
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
ALL_SOLC_VERSIONS_04,
DETECTOR_INFO,
)
from slither.formatters.attributes.const_functions import custom_format
from slither.utils.output import Output
@ -74,7 +77,7 @@ All the calls to `get` revert, breaking Bob's smart contract execution."""
if variables_written:
attr = "view" if f.view else "pure"
info = [
info: DETECTOR_INFO = [
f,
f" is declared {attr} but changes state variables:\n",
]
@ -89,5 +92,5 @@ All the calls to `get` revert, breaking Bob's smart contract execution."""
return results
@staticmethod
def _format(slither, result):
def _format(slither: SlitherCompilationUnit, result: Dict) -> None:
custom_format(slither, result)

@ -1,9 +1,14 @@
"""
Check that the same pragma is used in all the files
"""
from typing import List
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from typing import List, Dict
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.formatters.attributes.constant_pragma import custom_format
from slither.utils.output import Output
@ -31,7 +36,7 @@ class ConstantPragma(AbstractDetector):
versions = sorted(list(set(versions)))
if len(versions) > 1:
info = ["Different versions of Solidity are used:\n"]
info: DETECTOR_INFO = ["Different versions of Solidity are used:\n"]
info += [f"\t- Version used: {[str(v) for v in versions]}\n"]
for p in sorted(pragma, key=lambda x: x.version):
@ -44,5 +49,5 @@ class ConstantPragma(AbstractDetector):
return results
@staticmethod
def _format(slither, result):
def _format(slither: SlitherCompilationUnit, result: Dict) -> None:
custom_format(slither, result)

@ -5,7 +5,11 @@
import re
from typing import List, Optional, Tuple
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.formatters.attributes.incorrect_solc import custom_format
from slither.utils.output import Output
@ -43,10 +47,7 @@ We also recommend avoiding complex `pragma` statement."""
# region wiki_recommendation
WIKI_RECOMMENDATION = """
Deploy with any of the following Solidity versions:
- 0.5.16 - 0.5.17
- 0.6.11 - 0.6.12
- 0.7.5 - 0.7.6
- 0.8.16
- 0.8.18
The recommendations take into account:
- Risks related to recent releases
@ -62,13 +63,14 @@ Consider using the latest version of Solidity for testing."""
OLD_VERSION_TXT = "allows old versions"
LESS_THAN_TXT = "uses lesser than"
TOO_RECENT_VERSION_TXT = "necessitates a version too recent to be trusted. Consider deploying with 0.6.12/0.7.6/0.8.16"
BUGGY_VERSION_TXT = (
"is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)"
)
# Indicates the allowed versions. Must be formatted in increasing order.
ALLOWED_VERSIONS = ["0.5.16", "0.5.17", "0.6.11", "0.6.12", "0.7.5", "0.7.6", "0.8.16"]
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 = [
@ -143,7 +145,7 @@ Consider using the latest version of Solidity for testing."""
# If we found any disallowed pragmas, we output our findings.
if disallowed_pragmas:
for (reason, p) in disallowed_pragmas:
info = ["Pragma version", p, f" {reason}\n"]
info: DETECTOR_INFO = ["Pragma version", p, f" {reason}\n"]
json = self.generate_result(info)

@ -4,7 +4,11 @@
from typing import List
from slither.core.declarations.contract import Contract
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import (
HighLevelCall,
LowLevelCall,
@ -85,7 +89,7 @@ Every Ether sent to `Locked` will be lost."""
funcs_payable = [function for function in contract.functions if function.payable]
if funcs_payable:
if self.do_no_send_ether(contract):
info = ["Contract locking ether found:\n"]
info: DETECTOR_INFO = ["Contract locking ether found:\n"]
info += ["\tContract ", contract, " has payable functions:\n"]
for function in funcs_payable:
info += ["\t - ", function, "\n"]

@ -5,7 +5,11 @@ Collect all the interfaces
Check for contracts which implement all interface functions but do not explicitly derive from those interfaces.
"""
from typing import List
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.core.declarations.contract import Contract
from slither.utils.output import Output
@ -139,7 +143,7 @@ contract Something {
continue
intended_interfaces = self.detect_unimplemented_interface(contract, interfaces)
for interface in intended_interfaces:
info = [contract, " should inherit from ", interface, "\n"]
info: DETECTOR_INFO = [contract, " should inherit from ", interface, "\n"]
res = self.generate_result(info)
results.append(res)
return results

@ -2,7 +2,14 @@
Detects the passing of arrays located in memory to functions which expect to modify arrays via storage reference.
"""
from typing import List, Set, Tuple, Union
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations import Function
from slither.core.variables import Variable
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.core.solidity_types.array_type import ArrayType
from slither.core.variables.state_variable import StateVariable
from slither.core.variables.local_variable import LocalVariable
@ -89,12 +96,7 @@ As a result, Bob's usage of the contract is incorrect."""
@staticmethod
def detect_calls_passing_ref_to_function(
contracts: List[Contract], array_modifying_funcs: Set[FunctionContract]
) -> List[
Union[
Tuple[Node, StateVariable, FunctionContract],
Tuple[Node, LocalVariable, FunctionContract],
]
]:
) -> List[Tuple[Node, Variable, Union[Function, Variable]]]:
"""
Obtains all calls passing storage arrays by value to a function which cannot write to them successfully.
:param contracts: The collection of contracts to check for problematic calls in.
@ -105,7 +107,7 @@ As a result, Bob's usage of the contract is incorrect."""
write to the array unsuccessfully.
"""
# Define our resulting array.
results = []
results: List[Tuple[Node, Variable, Union[Function, Variable]]] = []
# Verify we have functions in our list to check for.
if not array_modifying_funcs:
@ -159,7 +161,7 @@ As a result, Bob's usage of the contract is incorrect."""
if problematic_calls:
for calling_node, affected_argument, invoked_function in problematic_calls:
info = [
info: DETECTOR_INFO = [
calling_node.function,
" passes array ",
affected_argument,

@ -10,6 +10,7 @@ from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
make_solc_versions,
DETECTOR_INFO,
)
from slither.slithir.operations import TypeConversion
from slither.core.declarations.enum import Enum
@ -73,10 +74,14 @@ Attackers can trigger unexpected behaviour by calling `bug(1)`."""
for c in self.compilation_unit.contracts:
ret = _detect_dangerous_enum_conversions(c)
for node, var in ret:
func_info = [node, " has a dangerous enum conversion\n"]
func_info: DETECTOR_INFO = [node, " has a dangerous enum conversion\n"]
# Output each node with the function info header as a separate result.
variable_info = ["\t- Variable: ", var, f" of type: {str(var.type)}\n"]
node_info = ["\t- Enum conversion: ", node, "\n"]
variable_info: DETECTOR_INFO = [
"\t- Variable: ",
var,
f" of type: {str(var.type)}\n",
]
node_info: DETECTOR_INFO = ["\t- Enum conversion: ", node, "\n"]
json = self.generate_result(func_info + variable_info + node_info)
results.append(json)

@ -1,6 +1,10 @@
from typing import List
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.utils.output import Output
@ -58,7 +62,10 @@ In Solidity [0.4.22](https://github.com/ethereum/solidity/releases/tag/v0.4.23),
# If there is more than one, we encountered the described issue occurring.
if constructors and len(constructors) > 1:
info = [contract, " contains multiple constructors in the same contract:\n"]
info: DETECTOR_INFO = [
contract,
" contains multiple constructors in the same contract:\n",
]
for constructor in constructors:
info += ["\t- ", constructor, "\n"]

@ -6,6 +6,7 @@ from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
ALL_SOLC_VERSIONS_04,
DETECTOR_INFO,
)
from slither.core.declarations.contract import Contract
from slither.core.declarations.function_contract import FunctionContract
@ -151,7 +152,7 @@ The constructor of `A` is called multiple times in `D` and `E`:
continue
# Generate data to output.
info = [
info: DETECTOR_INFO = [
contract,
" gives base constructor ",
base_constructor,

@ -6,6 +6,7 @@ from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
make_solc_versions,
DETECTOR_INFO,
)
from slither.core.solidity_types import ArrayType
from slither.core.solidity_types import UserDefinedType
@ -122,7 +123,13 @@ contract A {
for contract in self.contracts:
storage_abiencoderv2_arrays = self._detect_storage_abiencoderv2_arrays(contract)
for function, node in storage_abiencoderv2_arrays:
info = ["Function ", function, " trigger an abi encoding bug:\n\t- ", node, "\n"]
info: DETECTOR_INFO = [
"Function ",
function,
" trigger an abi encoding bug:\n\t- ",
node,
"\n",
]
res = self.generate_result(info)
results.append(res)

@ -1,18 +1,21 @@
"""
Module detecting storage signed integer array bug
"""
from typing import List
from typing import List, Tuple, Set
from slither.core.declarations import Function, Contract
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
make_solc_versions,
DETECTOR_INFO,
)
from slither.core.cfg.node import NodeType
from slither.core.cfg.node import NodeType, Node
from slither.core.solidity_types import ArrayType
from slither.core.solidity_types.elementary_type import Int, ElementaryType
from slither.core.variables.local_variable import LocalVariable
from slither.core.variables.state_variable import StateVariable
from slither.slithir.operations import Operation, OperationWithLValue
from slither.slithir.operations.assignment import Assignment
from slither.slithir.operations.init_array import InitArray
from slither.utils.output import Output
@ -60,7 +63,7 @@ contract A {
VULNERABLE_SOLC_VERSIONS = make_solc_versions(4, 7, 25) + make_solc_versions(5, 0, 9)
@staticmethod
def _is_vulnerable_type(ir):
def _is_vulnerable_type(ir: Operation) -> bool:
"""
Detect if the IR lvalue is a vulnerable type
Must be a storage allocation, and an array of Int
@ -68,23 +71,28 @@ contract A {
"""
# Storage allocation
# Base type is signed integer
if not isinstance(ir, OperationWithLValue):
return False
return (
(
isinstance(ir.lvalue, StateVariable)
or (isinstance(ir.lvalue, LocalVariable) and ir.lvalue.is_storage)
)
and isinstance(ir.lvalue.type.type, ElementaryType)
and ir.lvalue.type.type.type in Int
and isinstance(ir.lvalue.type.type, ElementaryType) # type: ignore
and ir.lvalue.type.type.type in Int # type: ignore
)
def detect_storage_signed_integer_arrays(self, contract):
def detect_storage_signed_integer_arrays(
self, contract: Contract
) -> Set[Tuple[Function, Node]]:
"""
Detects and returns all nodes with storage-allocated signed integer array init/assignment
:param contract: Contract to detect within
:return: A list of tuples with (function, node) where function node has storage-allocated signed integer array init/assignment
"""
# Create our result set.
results = set()
results: Set[Tuple[Function, Node]] = set()
# Loop for each function and modifier.
for function in contract.functions_and_modifiers_declared:
@ -118,9 +126,13 @@ contract A {
for contract in self.contracts:
storage_signed_integer_arrays = self.detect_storage_signed_integer_arrays(contract)
for function, node in storage_signed_integer_arrays:
contract_info = ["Contract ", contract, " \n"]
function_info = ["\t- Function ", function, "\n"]
node_info = ["\t\t- ", node, " has a storage signed integer array assignment\n"]
contract_info: DETECTOR_INFO = ["Contract ", contract, " \n"]
function_info: DETECTOR_INFO = ["\t- Function ", function, "\n"]
node_info: DETECTOR_INFO = [
"\t\t- ",
node,
" has a storage signed integer array assignment\n",
]
res = self.generate_result(contract_info + function_info + node_info)
results.append(res)

@ -6,6 +6,7 @@ from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
make_solc_versions,
DETECTOR_INFO,
)
from slither.slithir.operations import InternalDynamicCall, OperationWithLValue
from slither.slithir.variables import ReferenceVariable
@ -115,10 +116,10 @@ The call to `a(10)` will lead to unexpected behavior because function pointer `a
results = []
for contract in self.compilation_unit.contracts:
contract_info = ["Contract ", contract, " \n"]
contract_info: DETECTOR_INFO = ["Contract ", contract, " \n"]
nodes = self._detect_uninitialized_function_ptr_in_constructor(contract)
for node in nodes:
node_info = [
node_info: DETECTOR_INFO = [
"\t ",
node,
" is an unintialized function pointer call in a constructor\n",

@ -61,12 +61,12 @@ class ArbitrarySendErc20:
is_dependent(
ir.arguments[0],
SolidityVariableComposed("msg.sender"),
node.function.contract,
node,
)
or is_dependent(
ir.arguments[0],
SolidityVariable("this"),
node.function.contract,
node,
)
)
):
@ -79,12 +79,12 @@ class ArbitrarySendErc20:
is_dependent(
ir.arguments[1],
SolidityVariableComposed("msg.sender"),
node.function.contract,
node,
)
or is_dependent(
ir.arguments[1],
SolidityVariable("this"),
node.function.contract,
node,
)
)
):

@ -1,5 +1,9 @@
from typing import List
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.utils.output import Output
from .arbitrary_send_erc20 import ArbitrarySendErc20
@ -38,7 +42,7 @@ Use `msg.sender` as `from` in transferFrom.
arbitrary_sends.detect()
for node in arbitrary_sends.no_permit_results:
func = node.function
info = [func, " uses arbitrary from in transferFrom: ", node, "\n"]
info: DETECTOR_INFO = [func, " uses arbitrary from in transferFrom: ", node, "\n"]
res = self.generate_result(info)
results.append(res)

@ -1,5 +1,9 @@
from typing import List
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.utils.output import Output
from .arbitrary_send_erc20 import ArbitrarySendErc20
@ -41,7 +45,7 @@ Ensure that the underlying ERC20 token correctly implements a permit function.
arbitrary_sends.detect()
for node in arbitrary_sends.permit_results:
func = node.function
info = [
info: DETECTOR_INFO = [
func,
" uses arbitrary from in transferFrom in combination with permit: ",
node,

@ -6,7 +6,11 @@ from typing import List, Tuple
from slither.core.declarations.contract import Contract
from slither.core.declarations.function_contract import FunctionContract
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.utils.output import Output
@ -109,7 +113,7 @@ contract Token{
functions = IncorrectERC20InterfaceDetection.detect_incorrect_erc20_interface(c)
if functions:
for function in functions:
info = [
info: DETECTOR_INFO = [
c,
" has incorrect ERC20 function interface:",
function,

@ -2,7 +2,11 @@
Detect incorrect erc721 interface.
"""
from typing import Any, List, Tuple, Union
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.core.declarations.contract import Contract
from slither.core.declarations.function_contract import FunctionContract
from slither.utils.output import Output
@ -89,7 +93,9 @@ contract Token{
return False
@staticmethod
def detect_incorrect_erc721_interface(contract: Contract) -> List[Union[FunctionContract, Any]]:
def detect_incorrect_erc721_interface(
contract: Contract,
) -> List[Union[FunctionContract, Any]]:
"""Detect incorrect ERC721 interface
Returns:
@ -119,7 +125,7 @@ contract Token{
functions = IncorrectERC721InterfaceDetection.detect_incorrect_erc721_interface(c)
if functions:
for function in functions:
info = [
info: DETECTOR_INFO = [
c,
" has incorrect ERC721 function interface:",
function,

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

Loading…
Cancel
Save