type-improvemetns
alpharush 2 years ago
commit c8785d0547
  1. 38
      .github/DISCUSSION_TEMPLATE/installation.yml
  2. 2
      .github/ISSUE_TEMPLATE/bug_report.yml
  3. 61
      .github/ISSUE_TEMPLATE/false_negative.yml
  4. 61
      .github/ISSUE_TEMPLATE/false_positive.yml
  5. 30
      .github/actions/upload-coverage/action.yml
  6. 13
      .github/scripts/integration_test_runner.sh
  7. 13
      .github/scripts/tool_test_runner.sh
  8. 13
      .github/scripts/unit_test_runner.sh
  9. 47
      .github/workflows/IR.yml
  10. 15
      .github/workflows/black.yml
  11. 12
      .github/workflows/ci.yml
  12. 41
      .github/workflows/detectors.yml
  13. 4
      .github/workflows/docker.yml
  14. 46
      .github/workflows/docs.yml
  15. 6
      .github/workflows/doctor.yml
  16. 51
      .github/workflows/features.yml
  17. 16
      .github/workflows/linter.yml
  18. 45
      .github/workflows/parser.yml
  19. 4
      .github/workflows/pip-audit.yml
  20. 22
      .github/workflows/pylint.yml
  21. 50
      .github/workflows/read_storage.yml
  22. 107
      .github/workflows/test.yml
  23. 3
      .gitignore
  24. 146
      README.md
  25. 6
      examples/scripts/data_dependency.py
  26. 1
      examples/scripts/variable_in_condition.py
  27. 6
      scripts/ci_test_cli.sh
  28. 2
      scripts/ci_test_erc.sh
  29. 7
      scripts/ci_test_etherscan.sh
  30. 2
      scripts/ci_test_path_filtering.sh
  31. 15
      scripts/ci_test_printers.sh
  32. 28
      scripts/ci_test_upgradability.sh
  33. 20
      setup.py
  34. 94
      slither/__main__.py
  35. 146
      slither/analyses/data_dependency/data_dependency.py
  36. 29
      slither/analyses/write/are_variables_written.py
  37. 227
      slither/core/cfg/node.py
  38. 2
      slither/core/cfg/scope.py
  39. 19
      slither/core/children/child_contract.py
  40. 17
      slither/core/children/child_event.py
  41. 17
      slither/core/children/child_expression.py
  42. 17
      slither/core/children/child_function.py
  43. 17
      slither/core/children/child_inheritance.py
  44. 31
      slither/core/children/child_node.py
  45. 17
      slither/core/children/child_structure.py
  46. 31
      slither/core/compilation_unit.py
  47. 1
      slither/core/declarations/__init__.py
  48. 177
      slither/core/declarations/contract.py
  49. 29
      slither/core/declarations/contract_level.py
  50. 16
      slither/core/declarations/custom_error.py
  51. 12
      slither/core/declarations/custom_error_contract.py
  52. 2
      slither/core/declarations/custom_error_top_level.py
  53. 4
      slither/core/declarations/enum.py
  54. 4
      slither/core/declarations/enum_contract.py
  55. 4
      slither/core/declarations/enum_top_level.py
  56. 8
      slither/core/declarations/event.py
  57. 64
      slither/core/declarations/function.py
  58. 39
      slither/core/declarations/function_contract.py
  59. 9
      slither/core/declarations/function_top_level.py
  60. 2
      slither/core/declarations/import_directive.py
  61. 4
      slither/core/declarations/pragma_directive.py
  62. 8
      slither/core/declarations/solidity_import_placeholder.py
  63. 42
      slither/core/declarations/solidity_variables.py
  64. 14
      slither/core/declarations/structure.py
  65. 4
      slither/core/declarations/structure_contract.py
  66. 2
      slither/core/declarations/structure_top_level.py
  67. 6
      slither/core/declarations/top_level.py
  68. 5
      slither/core/declarations/using_for_top_level.py
  69. 13
      slither/core/dominators/utils.py
  70. 7
      slither/core/expressions/assignment_operation.py
  71. 3
      slither/core/expressions/binary_operation.py
  72. 14
      slither/core/expressions/call_expression.py
  73. 21
      slither/core/expressions/conditional_expression.py
  74. 5
      slither/core/expressions/elementary_type_name_expression.py
  75. 20
      slither/core/expressions/expression_typed.py
  76. 76
      slither/core/expressions/identifier.py
  77. 28
      slither/core/expressions/index_access.py
  78. 2
      slither/core/expressions/literal.py
  79. 7
      slither/core/expressions/member_access.py
  80. 11
      slither/core/expressions/new_array.py
  81. 4
      slither/core/expressions/new_contract.py
  82. 32
      slither/core/expressions/type_conversion.py
  83. 23
      slither/core/expressions/unary_operation.py
  84. 2
      slither/core/scope/scope.py
  85. 24
      slither/core/slither_core.py
  86. 28
      slither/core/solidity_types/array_type.py
  87. 10
      slither/core/solidity_types/elementary_type.py
  88. 6
      slither/core/solidity_types/function_type.py
  89. 18
      slither/core/solidity_types/mapping_type.py
  90. 20
      slither/core/solidity_types/type_alias.py
  91. 11
      slither/core/solidity_types/type_information.py
  92. 14
      slither/core/solidity_types/user_defined_type.py
  93. 77
      slither/core/source_mapping/source_mapping.py
  94. 2
      slither/core/variables/__init__.py
  95. 7
      slither/core/variables/event_variable.py
  96. 19
      slither/core/variables/local_variable.py
  97. 2
      slither/core/variables/local_variable_init_from_tuple.py
  98. 6
      slither/core/variables/state_variable.py
  99. 19
      slither/core/variables/structure_variable.py
  100. 2
      slither/core/variables/top_level_variable.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]: "

@ -4,6 +4,8 @@ body:
attributes:
value: |
Please check the issues tab to avoid duplicates.
If you are having difficulty installing slither,
please head over to the "Discussions" page.
Thanks for taking the time to fill out this bug report!
type: markdown
-

@ -0,0 +1,61 @@
---
body:
-
attributes:
value: |
Please check the issues tab to avoid duplicates.
Thanks for helping make Slither the best it can be!
type: markdown
-
attributes:
label: "What bug did Slither miss and which detector did you anticipate would catch it?"
id: what-happened
type: textarea
validations:
required: true
-
attributes:
label: Frequency
description: How often do you run across this false negative?
options:
- Very Frequently
- Occasionally
- Rarely
- Not sure
id: frequency
type: dropdown
validations:
required: true
-
attributes:
description: "It can be a github repo, etherscan link, or code snippet."
label: "Code example to reproduce the issue:"
placeholder: "`contract A {}`\n"
id: reproduce
type: textarea
validations:
required: true
-
attributes:
description: |
What version of slither are you running?
Run `slither --version`
label: "Version:"
id: version
type: textarea
validations:
required: true
-
attributes:
description: |
Please copy and paste the result output. This
will be automatically formatted into code, so no need for backticks.
render: shell
label: "Relevant log output:"
id: logs
type: textarea
description: "Slither missed a bug it should find."
labels:
- false-negative
name: False Negative
title: "[False Negative]: "

@ -0,0 +1,61 @@
---
body:
-
attributes:
value: |
Please check the issues tab to avoid duplicates.
Thanks for helping make Slither the best it can be!
type: markdown
-
attributes:
label: "Describe the false alarm that Slither raise and how you know it's inaccurate:"
id: what-happened
type: textarea
validations:
required: true
-
attributes:
label: Frequency
description: How often do you run across this false positive?
options:
- Very Frequently
- Occasionally
- Rarely
- Not sure
id: frequency
type: dropdown
validations:
required: true
-
attributes:
description: "It can be a github repo, etherscan link, or code snippet."
label: "Code example to reproduce the issue:"
placeholder: "`contract A {}`\n"
id: reproduce
type: textarea
validations:
required: true
-
attributes:
description: |
What version of slither are you running?
Run `slither --version`
label: "Version:"
id: version
type: textarea
validations:
required: true
-
attributes:
description: |
Please copy and paste the result output. This
will be automatically formatted into code, so no need for backticks.
render: shell
label: "Relevant log output:"
id: logs
type: textarea
description: "Slither warned of an issue that is not legitimate and does not need to be fixed."
labels:
- false-positive
name: "False Positive"
title: "[False-Positive]: "

@ -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/
status_code=$?
python -m coverage report
else
pytest tests/unit/
status_code=$?
fi
exit "$status_code"

@ -1,47 +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 * * *'
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@v2
- 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

@ -1,5 +1,5 @@
---
name: Lint Code Base
name: Run black
defaults:
run:
@ -9,18 +9,27 @@ defaults:
on:
pull_request:
branches: [master, dev]
paths:
- "**/*.py"
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: Lint Code Base
name: Black
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
# Full git history is needed to get a proper list of changed files within `super-linter`
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v3

@ -15,6 +15,10 @@ on:
# 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 }}
@ -47,23 +51,21 @@ jobs:
- os: windows-2022
type: truffle
steps:
- uses: actions/checkout@v1
- 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]"
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'
uses: cachix/install-nix-action@v16
uses: cachix/install-nix-action@v20
- name: Set up cachix
if: matrix.type == 'dapp'

@ -1,41 +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 * * *'
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@v2
- 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

@ -8,6 +8,10 @@ on:
tags:
- '*'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
docker:
runs-on: ubuntu-latest

@ -0,0 +1,46 @@
name: docs
on:
# Runs on pushes targeting the default branch
push:
branches: ["master"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow one concurrent deployment
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
# Single deploy job since we're just deploying
build:
environment:
name: Slither Documentation
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Pages
uses: actions/configure-pages@v3
- uses: actions/setup-python@v4
with:
python-version: '3.8'
- 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: './html/'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1

@ -1,5 +1,5 @@
---
name: CI
name: Doctor
defaults:
run:
@ -12,6 +12,10 @@ on:
- 'slither/tools/doctor/**'
- '.github/workflows/doctor.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
slither-doctor:
runs-on: ${{ matrix.os }}

@ -1,51 +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 * * *'
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@v2
- 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/test_functions_ids.py
pytest tests/test_function.py
pytest tests/test_source_mapping.py

@ -9,18 +9,28 @@ defaults:
on:
pull_request:
branches: [master, dev]
paths:
- "**/*.py"
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: Lint Code Base
name: Superlinter
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
# Full git history is needed to get a proper list of changed files within `super-linter`
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v3
@ -54,5 +64,7 @@ jobs:
VALIDATE_EDITORCONFIG: false
VALIDATE_JSCPD: false
VALIDATE_PYTHON_MYPY: false
# Until we upgrade the super linter for actionlintÒ
VALIDATE_GITHUB_ACTIONS: false
SHELLCHECK_OPTS: "-e SC1090"
FILTER_REGEX_EXCLUDE: .*tests/.*.(json|zip|sol)

@ -1,45 +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 * * *'
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@v2
- 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

@ -8,6 +8,10 @@ on:
branches: [ dev, master ]
schedule: [ cron: "0 7 * * 2" ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
audit:
runs-on: ubuntu-latest

@ -1,5 +1,5 @@
---
name: Lint Code Base
name: Run pylint
defaults:
run:
@ -9,9 +9,10 @@ defaults:
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:
@ -20,7 +21,10 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
# Full git history is needed to get a proper list of changed files within `super-linter`
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v3
@ -36,9 +40,11 @@ jobs:
uses: github/super-linter/slim@v4.9.2
if: always()
env:
# run linter on everything to catch preexisting problems
VALIDATE_ALL_CODEBASE: true
DEFAULT_BRANCH: master
# Run linters only on new files for pylint to speed up the CI
VALIDATE_ALL_CODEBASE: false
# Compare against the base branch
# This is only accessible on PR
DEFAULT_BRANCH: ${{ github.base_ref }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Run only pylint
VALIDATE_PYTHON: true

@ -1,50 +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 * * *'
jobs:
build:
name: Test slither-read-storage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- 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]"
pip install web3
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,107 @@
---
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]"
solc-select install 0.8.0
solc-select use 0.8.0
- 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

3
.gitignore vendored

@ -111,3 +111,6 @@ test_artifacts/
# crytic export
crytic-export/
# Auto-generated Github pages docs
docs/

@ -1,17 +1,19 @@
# Slither, the Solidity source analyzer
<img src="./logo.png" alt="Logo" width="500"/>
[![Build Status](https://img.shields.io/github/workflow/status/crytic/slither/CI/master)](https://github.com/crytic/slither/actions?query=workflow%3ACI)
[![Build Status](https://img.shields.io/github/actions/workflow/status/crytic/slither/ci.yml?branch=master)](https://github.com/crytic/slither/actions?query=workflow%3ACI)
[![Slack Status](https://empireslacking.herokuapp.com/badge.svg)](https://empireslacking.herokuapp.com)
[![PyPI version](https://badge.fury.io/py/slither-analyzer.svg)](https://badge.fury.io/py/slither-analyzer)
Slither is a Solidity static analysis framework written in Python 3. It runs a suite of vulnerability detectors, prints visual information about contract details, and provides an API to easily write custom analyses. Slither enables developers to find vulnerabilities, enhance their code comprehension, and quickly prototype custom analyses.
Slither is a Solidity static analysis framework written in Python3. It runs a suite of vulnerability detectors, prints visual information about contract details, and provides an API to easily write custom analyses. Slither enables developers to find vulnerabilities, enhance their code comprehension, and quickly prototype custom analyses.
- [Features](#features)
- [Bugs and Optimizations Detection](#bugs-and-optimizations-detection)
- [Usage](#usage)
- [How to Install](#how-to-install)
- [Detectors](#detectors)
- [Printers](#printers)
- [Tools](#tools)
- [How to Install](#how-to-install)
- [API Documentation](#api-documentation)
- [Getting Help](#getting-help)
- [FAQ](#faq)
- [Publications](#publications)
@ -20,35 +22,68 @@ Slither is a Solidity static analysis framework written in Python 3. It runs a s
* Detects vulnerable Solidity code with low false positives (see the list of [trophies](./trophies.md))
* Identifies where the error condition occurs in the source code
* Easily integrates into continuous integration and Truffle builds
* Easily integrates into continuous integration and Hardhat/Foundry builds
* Built-in 'printers' quickly report crucial contract information
* Detector API to write custom analyses in Python
* Ability to analyze contracts written with Solidity >= 0.4
* Intermediate representation ([SlithIR](https://github.com/trailofbits/slither/wiki/SlithIR)) enables simple, high-precision analyses
* Correctly parses 99.9% of all public Solidity code
* Average execution time of less than 1 second per contract
* Integrates with Github's code scanning in [CI](https://github.com/marketplace/actions/slither-action)
## Usage
## Bugs and Optimizations Detection
Run Slither on a Truffle/Embark/Dapp/Etherlime/Hardhat application:
Run Slither on a Hardhat/Foundry/Dapp/Brownie application:
```bash
slither .
```
This is the preferred option if your project has dependencies as Slither relies on the underlying compilation framework to compile source code.
Run Slither on a single file:
However, you can run Slither on a single file that does not import dependencies:
```bash
slither tests/uninitialized.sol
```
## How to install
Slither requires Python 3.8+.
If you're **not** going to use one of the [supported compilation frameworks](https://github.com/crytic/crytic-compile), you need [solc](https://github.com/ethereum/solidity/), the Solidity compiler; we recommend using [solc-select](https://github.com/crytic/solc-select) to conveniently switch between solc versions.
### Using Pip
```bash
pip3 install slither-analyzer
```
### Using Git
```bash
git clone https://github.com/crytic/slither.git && cd slither
python3 setup.py install
```
We recommend using a Python virtual environment, as detailed in the [Developer Installation Instructions](https://github.com/trailofbits/slither/wiki/Developer-installation), if you prefer to install Slither via git.
### Using Docker
Use the [`eth-security-toolbox`](https://github.com/trailofbits/eth-security-toolbox/) docker image. It includes all of our security tools and every major version of Solidity in a single image. `/home/share` will be mounted to `/share` in the container.
```bash
docker pull trailofbits/eth-security-toolbox
```
To share a directory in the container:
```bash
docker run -it -v /home/share:/share trailofbits/eth-security-toolbox
```
### Integration
- For GitHub action integration, use [slither-action](https://github.com/marketplace/actions/slither-action).
- To generate a Markdown report, use `slither [target] --checklist`.
- To generate a Markdown with GitHub source code highlighting, use `slither [target] --checklist --markdown-root https://github.com/ORG/REPO/blob/COMMIT/` (replace `ORG`, `REPO`, `COMMIT`)
Use [solc-select](https://github.com/crytic/solc-select) if your contracts require older versions of solc. For additional configuration, see the [usage](https://github.com/trailofbits/slither/wiki/Usage) documentation.
### Detectors
## Detectors
Num | Detector | What it Detects | Impact | Confidence
@ -116,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
@ -173,38 +209,8 @@ See the [Tool documentation](https://github.com/crytic/slither/wiki/Tool-Documen
[Contact us](https://www.trailofbits.com/contact/) to get help on building custom tools.
## How to install
Slither requires Python 3.8+ and [solc](https://github.com/ethereum/solidity/), the Solidity compiler.
### Using Pip
```bash
pip3 install slither-analyzer
```
### Using Git
```bash
git clone https://github.com/crytic/slither.git && cd slither
python3 setup.py install
```
We recommend using a Python virtual environment, as detailed in the [Developer Installation Instructions](https://github.com/trailofbits/slither/wiki/Developer-installation), if you prefer to install Slither via git.
### Using Docker
Use the [`eth-security-toolbox`](https://github.com/trailofbits/eth-security-toolbox/) docker image. It includes all of our security tools and every major version of Solidity in a single image. `/home/share` will be mounted to `/share` in the container.
```bash
docker pull trailofbits/eth-security-toolbox
```
To share a directory in the container:
```bash
docker run -it -v /home/share:/share trailofbits/eth-security-toolbox
```
## API Documentation
Documentation on Slither's internals is available [here](https://crytic.github.io/slither/slither.html).
## Getting Help
@ -241,17 +247,19 @@ 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
--- | --- | --- | ---
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: Combining Static and Symbolic Analysis forScalable Testing of Smart Contract](https://arxiv.org/pdf/1911.00570.pdf) | Leverage data dependency through Slither | William Zhang, Sebastian Banescu, Leodardo Pasos, Steven Stewart, Vijay Ganesh | ISSRE 2019 | [MPro](https://github.com/QuanZhang-William/M-Pro)
[ETHPLOIT: From Fuzzing to Efficient Exploit Generation against Smart Contracts](https://wcventure.github.io/FuzzingPaper/Paper/SANER20_ETHPLOIT.pdf) | Leverage data dependency through Slither | Qingzhao Zhang, Yizhuo Wang, Juanru Li, Siqi Ma | SANER 20
[Verification of Ethereum Smart Contracts: A Model Checking Approach](http://www.ijmlc.org/vol10/977-AM0059.pdf) | Symbolic execution built on top of Slither’s CFG | Tam Bang, Hoang H Nguyen, Dung Nguyen, Toan Trieu, Tho Quan | IJMLC 20
[Smart Contract Repair](https://arxiv.org/pdf/1912.05823.pdf) | Rely on Slither’s vulnerabilities detectors | Xiao Liang Yu, Omar Al-Bataineh, David Lo, Abhik Roychoudhury | TOSEM 20
[Smart Contract Repair](https://arxiv.org/pdf/1912.05823.pdf) | Rely on Slither’s vulnerabilities detectors | Xiao Liang Yu, Omar Al-Bataineh, David Lo, Abhik Roychoudhury | TOSEM 20 | [SCRepair](https://github.com/xiaoly8/SCRepair/)
[Demystifying Loops in Smart Contracts](https://www.microsoft.com/en-us/research/uploads/prod/2020/08/loops_solidity__camera_ready-5f3fec3f15c69.pdf) | Leverage data dependency through Slither | Ben Mariano, Yanju Chen, Yu Feng, Shuvendu Lahiri, Isil Dillig | ASE 20
[Trace-Based Dynamic Gas Estimation of Loops in Smart Contracts](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9268144) | Use Slither’s CFG to detect loops | Chunmiao Li, Shijie Nie, Yang Cao, Yijun Yu, Zhenjiang Hu | IEEE Open J. Comput. Soc. 1 (2020)
[SAILFISH: Vetting Smart Contract State-Inconsistency Bugs in Seconds](https://arxiv.org/pdf/2104.08638.pdf) | Rely on SlithIR to build a *storage dependency graph* | Priyanka Bose, Dipanjan Das, Yanju Chen, Yu Feng, Christopher Kruegel, and Giovanni Vigna | S&P 22
[SAILFISH: Vetting Smart Contract State-Inconsistency Bugs in Seconds](https://arxiv.org/pdf/2104.08638.pdf) | Rely on SlithIR to build a *storage dependency graph* | Priyanka Bose, Dipanjan Das, Yanju Chen, Yu Feng, Christopher Kruegel, and Giovanni Vigna | S&P 22 | [Sailfish](https://github.com/ucsb-seclab/sailfish)
[SolType: Refinement Types for Arithmetic Overflow in Solidity](https://arxiv.org/abs/2110.00677) | Use Slither as frontend to build refinement type system | Bryan Tan, Benjamin Mariano, Shuvendu K. Lahiri, Isil Dillig, Yu Feng | POPL 22
[Do Not Rug on Me: Leveraging Machine Learning Techniques for Automated Scam Detection](https://www.mdpi.com/2227-7390/10/6/949) | Use Slither to extract tokens' features (mintable, pausable, ..) | Mazorra, Bruno, Victor Adan, and Vanesa Daza | Mathematics 10.6 (2022)
[MANDO: Multi-Level Heterogeneous Graph Embeddings for Fine-Grained Detection of Smart Contract Vulnerabilities](https://arxiv.org/abs/2208.13252) | Use Slither to extract the CFG and call graph | Hoang Nguyen, Nhat-Minh Nguyen, Chunyao Xie, Zahra Ahmadi, Daniel Kudendo, Thanh-Nam Doan and Lingxiao Jiang| IEEE 9th International Conference on Data Science and Advanced Analytics (DSAA, 2022) | [ge-sc](https://github.com/MANDO-Project/ge-sc)
[Automated Auditing of Price Gouging TOD Vulnerabilities in Smart Contracts](https://www.cs.toronto.edu/~fanl/papers/price-icbc22.pdf) | Use Slither to extract the CFG and data dependencies| Sidi Mohamed Beillahi, Eric Keilty, Keerthi Nelaturu, Andreas Veneris, and Fan Long | 2022 IEEE International Conference on Blockchain and Cryptocurrency (ICBC) | [Smart-Contract-Repair](https://github.com/Veneris-Group/TOD-Location-Rectification)
If you are using Slither on an academic work, consider applying to the [Crytic $10k Research Prize](https://blog.trailofbits.com/2019/11/13/announcing-the-crytic-10k-research-prize/).

@ -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

@ -17,12 +17,5 @@ if [ "$GITHUB_ETHERSCAN" = "" ]; then
sleep $(( ( RANDOM % 5 ) + 1 ))s
fi
echo "::group::Etherscan rinkeby"
if ! slither rinkeby:0xFe05820C5A92D9bc906D4A46F662dbeba794d3b7 --etherscan-apikey "$GITHUB_ETHERSCAN" --no-fail-pedantic; then
echo "Etherscan rinkeby test failed"
exit 1
fi
echo "::endgroup::"
exit 0

@ -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,14 +2,23 @@
### 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"
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"
# Only test 0.5.17 to limit test time
for file in *0.5.17-compact.zip; do
if ! slither "$file" --print "$ALL_PRINTERS" > /dev/null 2>&1 ; then
if ! slither "$file" --print "$ALL_PRINTERS" ; then
echo "Printer failed"
echo "$file"
exit 1
fi
done
# Only test 0.8.12 to limit test time
for file in *0.8.12-compact.zip; do
if ! slither "$file" --print "declaration" ; then
echo "Printer failed"
echo "$file"
exit 1

@ -155,6 +155,32 @@ then
exit 255
fi
slither-check-upgradeability "$DIR_TESTS/contractV1_struct.sol" ContractV1 --new-contract-filename "$DIR_TESTS/contractV2_struct.sol" --new-contract-name ContractV2 > test_12.txt 2>&1
DIFF=$(diff test_12.txt "$DIR_TESTS/test_12.txt")
if [ "$DIFF" != "" ]
then
echo "slither-check-upgradeability 12 failed"
cat test_12.txt
echo ""
cat "$DIR_TESTS/test_12.txt"
echo ""
echo "$DIFF"
exit 255
fi
slither-check-upgradeability "$DIR_TESTS/contractV1_struct.sol" ContractV1 --new-contract-filename "$DIR_TESTS/contractV2_struct_bug.sol" --new-contract-name ContractV2 > test_13.txt 2>&1
DIFF=$(diff test_13.txt "$DIR_TESTS/test_13.txt")
if [ "$DIFF" != "" ]
then
echo "slither-check-upgradeability 13 failed"
cat test_13.txt
echo ""
cat "$DIR_TESTS/test_13.txt"
echo ""
echo "$DIFF"
exit 255
fi
rm test_1.txt
rm test_2.txt
rm test_3.txt
@ -166,3 +192,5 @@ rm test_8.txt
rm test_9.txt
rm test_10.txt
rm test_11.txt
rm test_12.txt
rm test_13.txt

@ -8,27 +8,37 @@ 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>=0.3.1,<0.4.0",
# "crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#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",
"solc-select>=v1.0.0b1",
"coverage[toml]",
],
"doc": [
"pdoc",
],
"dev": [
"slither-analyzer[lint,test,doc]",
"openai",
]
],
},
license="AGPL-3.0",
long_description=long_description,

@ -25,7 +25,13 @@ from slither.printers import all_printers
from slither.printers.abstract_printer import AbstractPrinter
from slither.slither import Slither
from slither.utils import codex
from slither.utils.output import output_to_json, output_to_zip, output_to_sarif, ZIP_TYPES_ACCEPTED
from slither.utils.output import (
output_to_json,
output_to_zip,
output_to_sarif,
ZIP_TYPES_ACCEPTED,
Output,
)
from slither.utils.output_capture import StandardOutputCapture
from slither.utils.colors import red, set_colorization_enabled
from slither.utils.command_line import (
@ -60,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.
@ -70,9 +76,6 @@ def process_single(
ast = "--ast-compact-json"
if args.legacy_ast:
ast = "--ast-json"
if args.checklist:
args.show_ignored_findings = True
slither = Slither(target, ast_format=ast, **vars(args))
return _process(slither, detector_classes, printer_classes)
@ -83,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 = []
@ -112,7 +115,7 @@ def _process(
slither: Slither,
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[Slither, List[Dict], List[Dict], int]:
) -> Tuple[Slither, List[Dict], List[Output], int]:
for detector_cls in detector_classes:
slither.register_detector(detector_cls)
@ -125,9 +128,9 @@ def _process(
results_printers = []
if not printer_classes:
detector_results = slither.run_detectors()
detector_results = [x for x in detector_results if x] # remove empty results
detector_results = [item for sublist in detector_results for item in sublist] # flatten
detector_resultss = slither.run_detectors()
detector_resultss = [x for x in detector_resultss if x] # remove empty results
detector_results = [item for sublist in detector_resultss for item in sublist] # flatten
results_detectors.extend(detector_results)
else:
@ -138,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
###################################################################################
###################################################################################
@ -511,7 +497,7 @@ def parse_args(
group_misc.add_argument(
"--filter-paths",
help="Comma-separated list of paths for which results will be excluded",
help="Regex filter to exclude detector results matching file path e.g. (mocks/|test/)",
action="store",
dest="filter_paths",
default=defaults_flag_in_config["filter_paths"],
@ -602,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
@ -620,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.')
@ -629,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()
@ -691,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)
@ -741,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 == "-"
@ -754,7 +739,7 @@ def main_impl(
# If we are outputting JSON, capture all standard output. If we are outputting to stdout, we block typical stdout
# output.
if outputting_json or output_to_sarif:
if outputting_json or outputting_sarif:
StandardOutputCapture.enable(outputting_json_stdout or outputting_sarif_stdout)
printer_classes = choose_printers(args, all_printer_classes)
@ -790,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
@ -803,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:
@ -865,7 +841,9 @@ def main_impl(
# Output our results to markdown if we wish to compile a checklist.
if args.checklist:
output_results_to_markdown(results_detectors, args.checklist_limit)
output_results_to_markdown(
results_detectors, args.checklist_limit, args.show_ignored_findings
)
# Don't print the number of result for printers
if number_contracts == 0:

@ -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
@ -111,12 +133,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)
@ -124,7 +149,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
@ -138,12 +166,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)
@ -151,7 +182,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
@ -165,19 +199,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())
@ -185,16 +224,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]
@ -202,19 +246,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())
@ -222,16 +271,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]
@ -341,13 +395,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
@ -366,20 +416,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:
@ -407,7 +457,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,
(
@ -438,10 +488,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

@ -4,16 +4,18 @@
from enum import Enum
from typing import Optional, List, Set, Dict, Tuple, Union, TYPE_CHECKING
from slither.core.children.child_function import ChildFunction
from slither.all_exceptions import SlitherException
from slither.core.declarations import Contract, Function, FunctionContract
from slither.core.declarations.solidity_variables import (
SolidityVariable,
SolidityFunction,
)
from slither.core.expressions.expression import Expression
from slither.core.solidity_types import ElementaryType
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.local_variable import LocalVariable
from slither.core.variables.state_variable import StateVariable
from slither.core.variables.variable import Variable
from slither.core.solidity_types import ElementaryType
from slither.slithir.convert import convert_expression
from slither.slithir.operations import (
HighLevelCall,
@ -30,6 +32,7 @@ from slither.slithir.operations import (
Return,
Operation,
)
from slither.slithir.utils.utils import RVALUE
from slither.slithir.variables import (
Constant,
LocalIRVariable,
@ -38,10 +41,6 @@ from slither.slithir.variables import (
TemporaryVariable,
TupleVariable,
)
from slither.all_exceptions import SlitherException
from slither.core.declarations import Contract, Function
from slither.core.expressions.expression import Expression
if TYPE_CHECKING:
from slither.slithir.variables.variable import SlithIRVariable
@ -66,87 +65,48 @@ if TYPE_CHECKING:
class NodeType(Enum):
ENTRYPOINT = 0x0 # no expression
ENTRYPOINT = "ENTRY_POINT" # no expression
# Node with expression
# Nodes that may have an expression
EXPRESSION = 0x10 # normal case
RETURN = 0x11 # RETURN may contain an expression
IF = 0x12
VARIABLE = 0x13 # Declaration of variable
ASSEMBLY = 0x14
IFLOOP = 0x15
EXPRESSION = "EXPRESSION" # normal case
RETURN = "RETURN" # RETURN may contain an expression
IF = "IF"
VARIABLE = "NEW VARIABLE" # Variable declaration
ASSEMBLY = "INLINE ASM"
IFLOOP = "IF_LOOP"
# Merging nodes
# Nodes where control flow merges
# Can have phi IR operation
ENDIF = 0x50 # ENDIF node source mapping points to the if/else body
STARTLOOP = 0x51 # STARTLOOP node source mapping points to the entire loop body
ENDLOOP = 0x52 # ENDLOOP node source mapping points to the entire loop body
ENDIF = "END_IF" # ENDIF node source mapping points to the if/else "body"
STARTLOOP = "BEGIN_LOOP" # STARTLOOP node source mapping points to the entire loop "body"
ENDLOOP = "END_LOOP" # ENDLOOP node source mapping points to the entire loop "body"
# Below the nodes have no expression
# But are used to expression CFG structure
# Below the nodes do not have an expression but are used to expression CFG structure.
# Absorbing node
THROW = 0x20
THROW = "THROW"
# Loop related nodes
BREAK = 0x31
CONTINUE = 0x32
BREAK = "BREAK"
CONTINUE = "CONTINUE"
# Only modifier node
PLACEHOLDER = 0x40
PLACEHOLDER = "_"
TRY = 0x41
CATCH = 0x42
TRY = "TRY"
CATCH = "CATCH"
# Node not related to the CFG
# Use for state variable declaration
OTHER_ENTRYPOINT = 0x60
# @staticmethod
def __str__(self):
if self == NodeType.ENTRYPOINT:
return "ENTRY_POINT"
if self == NodeType.EXPRESSION:
return "EXPRESSION"
if self == NodeType.RETURN:
return "RETURN"
if self == NodeType.IF:
return "IF"
if self == NodeType.VARIABLE:
return "NEW VARIABLE"
if self == NodeType.ASSEMBLY:
return "INLINE ASM"
if self == NodeType.IFLOOP:
return "IF_LOOP"
if self == NodeType.THROW:
return "THROW"
if self == NodeType.BREAK:
return "BREAK"
if self == NodeType.CONTINUE:
return "CONTINUE"
if self == NodeType.PLACEHOLDER:
return "_"
if self == NodeType.TRY:
return "TRY"
if self == NodeType.CATCH:
return "CATCH"
if self == NodeType.ENDIF:
return "END_IF"
if self == NodeType.STARTLOOP:
return "BEGIN_LOOP"
if self == NodeType.ENDLOOP:
return "END_LOOP"
if self == NodeType.OTHER_ENTRYPOINT:
return "OTHER_ENTRYPOINT"
return f"Unknown type {hex(self.value)}"
OTHER_ENTRYPOINT = "OTHER_ENTRYPOINT"
# endregion
# 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
@ -158,7 +118,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
node_id: int,
scope: Union["Scope", "Function"],
file_scope: "FileScope",
):
) -> None:
super().__init__()
self._node_type = node_type
@ -186,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"] = []
@ -212,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] = []
@ -229,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
###################################################################################
###################################################################################
@ -253,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
@ -279,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)
"""
@ -332,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
@ -386,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
@ -446,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
@ -457,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
@ -465,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
@ -520,11 +485,11 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
"""
return self._expression
def add_expression(self, expression: Expression, bypass_verif_empty: bool = False):
def add_expression(self, expression: Expression, bypass_verif_empty: bool = False) -> None:
assert self._expression is None or bypass_verif_empty
self._expression = expression
def add_variable_declaration(self, var: LocalVariable):
def add_variable_declaration(self, var: LocalVariable) -> None:
assert self._variable_declaration is None
self._variable_declaration = var
if var.expression:
@ -557,7 +522,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
for c in self.internal_calls
)
def contains_if(self, include_loop=True) -> bool:
def contains_if(self, include_loop: bool = True) -> bool:
"""
Check if the node is a IF node
Returns:
@ -567,7 +532,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self.type in [NodeType.IF, NodeType.IFLOOP]
return self.type == NodeType.IF
def is_conditional(self, include_loop=True) -> bool:
def is_conditional(self, include_loop: bool = True) -> bool:
"""
Check if the node is a conditional node
A conditional node is either a IF or a require/assert or a RETURN bool
@ -596,7 +561,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
def inline_asm(self) -> Optional[Union[str, Dict]]:
return self._asm_source_code
def add_inline_asm(self, asm: Union[str, Dict]):
def add_inline_asm(self, asm: Union[str, Dict]) -> None:
self._asm_source_code = asm
# endregion
@ -606,7 +571,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
###################################################################################
###################################################################################
def add_father(self, father: "Node"):
def add_father(self, father: "Node") -> None:
"""Add a father node
Args:
@ -614,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:
@ -631,7 +596,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
"""
return list(self._fathers)
def remove_father(self, father: "Node"):
def remove_father(self, father: "Node") -> None:
"""Remove the father node. Do nothing if the node is not a father
Args:
@ -639,7 +604,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
"""
self._fathers = [x for x in self._fathers if x.node_id != father.node_id]
def remove_son(self, son: "Node"):
def remove_son(self, son: "Node") -> None:
"""Remove the son node. Do nothing if the node is not a son
Args:
@ -647,7 +612,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
"""
self._sons = [x for x in self._sons if x.node_id != son.node_id]
def add_son(self, son: "Node"):
def add_son(self, son: "Node") -> None:
"""Add a son node
Args:
@ -655,7 +620,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
"""
self._sons.append(son)
def set_sons(self, sons: List["Node"]):
def set_sons(self, sons: List["Node"]) -> None:
"""Set the son nodes
Args:
@ -710,20 +675,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):
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):
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()
@ -737,11 +702,11 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._all_slithir_operations
@staticmethod
def _is_non_slithir_var(var: Variable):
def _is_non_slithir_var(var: Variable) -> bool:
return not isinstance(var, (Constant, ReferenceVariable, TemporaryVariable, TupleVariable))
@staticmethod
def _is_valid_slithir_var(var: Variable):
def _is_valid_slithir_var(var: Variable) -> bool:
return isinstance(var, (ReferenceVariable, TemporaryVariable, TupleVariable))
# endregion
@ -760,7 +725,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
@ -772,7 +737,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
@ -784,7 +749,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)
@ -792,7 +757,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._dominance_frontier = doms
@property
def dominator_successors(self):
def dominator_successors(self) -> Set["Node"]:
return self._dom_successors
@property
@ -834,14 +799,15 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
# def phi_origin_member_variables(self) -> Dict[str, Tuple[MemberVariable, Set["Node"]]]:
# return self._phi_origins_member_variables
def add_phi_origin_local_variable(self, variable: LocalVariable, node: "Node"):
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
nodes.add(node)
def add_phi_origin_state_variable(self, variable: StateVariable, node: "Node"):
def add_phi_origin_state_variable(self, variable: StateVariable, node: "Node") -> None:
if variable.canonical_name not in self._phi_origins_state_variables:
self._phi_origins_state_variables[variable.canonical_name] = (
variable,
@ -865,7 +831,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
###################################################################################
###################################################################################
def _find_read_write_call(self): # pylint: disable=too-many-statements
def _find_read_write_call(self) -> None: # pylint: disable=too-many-statements
for ir in self.irs:
@ -874,7 +840,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)]
@ -882,8 +849,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
@ -907,14 +875,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
@ -930,7 +905,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)]
@ -941,17 +918,20 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._low_level_calls = list(set(self._low_level_calls))
@staticmethod
def _convert_ssa(v: Variable):
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
def update_read_write_using_ssa(self):
def update_read_write_using_ssa(self) -> None:
if not self.expression:
return
for ir in self.irs_ssa:
@ -968,10 +948,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)
@ -991,20 +972,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)]
@ -1015,13 +996,13 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
###################################################################################
###################################################################################
def __str__(self):
def __str__(self) -> str:
additional_info = ""
if self.expression:
additional_info += " " + str(self.expression)
elif self.variable_declaration:
additional_info += " " + str(self.variable_declaration)
txt = str(self._node_type) + additional_info
txt = str(self._node_type.value) + additional_info
return txt
@ -1033,12 +1014,12 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
###################################################################################
def link_nodes(node1: Node, node2: Node):
def link_nodes(node1: Node, node2: Node) -> None:
node1.add_son(node2)
node2.add_father(node1)
def insert_node(origin: Node, node_inserted: Node):
def insert_node(origin: Node, node_inserted: Node) -> None:
sons = origin.sons
link_nodes(origin, node_inserted)
for son in sons:

@ -7,7 +7,7 @@ if TYPE_CHECKING:
# pylint: disable=too-few-public-methods
class Scope:
def __init__(self, is_checked: bool, is_yul: bool, scope: Union["Scope", "Function"]):
def __init__(self, is_checked: bool, is_yul: bool, scope: Union["Scope", "Function"]) -> None:
self.nodes: List["Node"] = []
self.is_checked = is_checked
self.is_yul = is_yul

@ -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):
super().__init__()
self._contract = None
def set_contract(self, contract: "Contract"):
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):
super().__init__()
self._event = None
def set_event(self, event: "Event"):
self._event = event
@property
def event(self) -> "Event":
return self._event

@ -1,17 +0,0 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from slither.core.expressions.expression import Expression
class ChildExpression:
def __init__(self):
super().__init__()
self._expression = None
def set_expression(self, expression: "Expression"):
self._expression = expression
@property
def expression(self) -> "Expression":
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):
super().__init__()
self._contract_declarer = None
def set_contract_declarer(self, contract: "Contract"):
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):
super().__init__()
self._node = None
def set_node(self, node: "Node"):
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):
super().__init__()
self._structure = None
def set_structure(self, structure: "Structure"):
self._structure = structure
@property
def structure(self) -> "Structure":
return self._structure

@ -16,10 +16,10 @@ from slither.core.declarations import (
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.using_for_top_level import UsingForTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel
from slither.core.solidity_types.type_alias import TypeAliasTopLevel
from slither.core.declarations.using_for_top_level import UsingForTopLevel
from slither.core.scope.scope import FileScope
from slither.core.solidity_types.type_alias import TypeAliasTopLevel
from slither.core.variables.state_variable import StateVariable
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.slithir.operations import InternalCall
@ -31,7 +31,7 @@ if TYPE_CHECKING:
# pylint: disable=too-many-instance-attributes,too-many-public-methods
class SlitherCompilationUnit(Context):
def __init__(self, core: "SlitherCore", crytic_compilation_unit: CompilationUnit):
def __init__(self, core: "SlitherCore", crytic_compilation_unit: CompilationUnit) -> None:
super().__init__()
self._core = core
@ -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:
@ -150,25 +151,26 @@ class SlitherCompilationUnit(Context):
def functions(self) -> List[Function]:
return list(self._all_functions)
def add_function(self, func: Function):
def add_function(self, func: Function) -> None:
self._all_functions.add(func)
@property
def modifiers(self) -> List[Modifier]:
return list(self._all_modifiers)
def add_modifier(self, modif: Modifier):
def add_modifier(self, modif: Modifier) -> None:
self._all_modifiers.add(modif)
@property
def functions_and_modifiers(self) -> List[Function | Modifier]:
def functions_and_modifiers(self) -> List[Union[Function, Modifier]]:
return self.functions + self.modifiers
def propagate_function_calls(self):
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
@ -256,8 +258,8 @@ class SlitherCompilationUnit(Context):
###################################################################################
###################################################################################
def compute_storage_layout(self):
for contract in self.contracts:
def compute_storage_layout(self) -> None:
for contract in self.contracts_derived:
self._storage_layouts[contract.name] = {}
slot = 0
@ -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

@ -17,3 +17,4 @@ from .structure_contract import StructureContract
from .structure_top_level import StructureTopLevel
from .function_contract import FunctionContract
from .function_top_level import FunctionTopLevel
from .custom_error_contract import CustomErrorContract

@ -4,7 +4,7 @@
import logging
from collections import defaultdict
from pathlib import Path
from typing import Optional, List, Dict, Callable, Tuple, TYPE_CHECKING, Union, Set
from typing import Optional, List, Dict, Callable, Tuple, TYPE_CHECKING, Union, Set, Any
from crytic_compile.platform import Type as PlatformType
@ -38,24 +38,27 @@ if TYPE_CHECKING:
EnumContract,
StructureContract,
FunctionContract,
CustomErrorContract,
)
from slither.slithir.variables.variable import SlithIRVariable
from slither.core.variables.variable import Variable
from slither.core.variables.state_variable import StateVariable
from slither.core.variables import Variable, StateVariable
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.declarations.custom_error_contract import CustomErrorContract
from slither.core.scope.scope import FileScope
from slither.core.cfg.node import Node
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
"""
Contract class
"""
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope"):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope") -> None:
super().__init__()
self._name: Optional[str] = None
@ -80,15 +83,19 @@ 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
@ -110,6 +117,8 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
Dict["StateVariable", Set[Union["StateVariable", "Function"]]]
] = None
self._comments: Optional[str] = None
###################################################################################
###################################################################################
# region General's properties
@ -123,7 +132,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
@ -133,7 +142,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
@ -146,7 +155,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
@ -154,7 +163,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
@ -162,9 +171,42 @@ 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
def comments(self) -> Optional[str]:
"""
Return the comments associated with the contract.
When using comments, avoid strict text matching, as the solc behavior might change.
For example, for old solc version, the first space after the * is not kept, i.e:
* @title Test Contract
* @dev Test comment
Returns
- " @title Test Contract\n @dev Test comment" for newest versions
- "@title Test Contract\n@dev Test comment" for older versions
Returns:
the comment as a string
"""
return self._comments
@comments.setter
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
###################################################################################
###################################################################################
@ -266,16 +308,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:
@ -366,7 +410,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
return list(self._variables_ordered)
def add_variables_ordered(self, new_vars: List["StateVariable"]):
def add_variables_ordered(self, new_vars: List["StateVariable"]) -> None:
self._variables_ordered += new_vars
@property
@ -455,7 +499,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)
"""
@ -524,17 +568,17 @@ 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"]):
def set_functions(self, functions: Dict[str, "FunctionContract"]) -> None:
"""
Set the functions
@ -578,7 +622,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def available_modifiers_as_dict(self) -> Dict[str, "Modifier"]:
return {m.full_name: m for m in self._modifiers.values() if not m.is_shadowed}
def set_modifiers(self, modifiers: Dict[str, "Modifier"]):
def set_modifiers(self, modifiers: Dict[str, "Modifier"]) -> None:
"""
Set the modifiers
@ -622,6 +666,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"],
@ -688,7 +750,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
inheritance: List["Contract"],
immediate_inheritance: List["Contract"],
called_base_constructor_contracts: List["Contract"],
):
) -> None:
self._inheritance = inheritance
self._immediate_inheritance = immediate_inheritance
self._explicit_base_constructor_calls = called_base_constructor_contracts
@ -699,7 +761,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
###################################################################################
@ -803,23 +865,25 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
return next((v for v in self.state_variables if v.name == canonical_name), None)
def get_structure_from_name(self, structure_name: str) -> Optional["Structure"]:
def get_structure_from_name(self, structure_name: str) -> Optional["StructureContract"]:
"""
Return a structure from a name
Args:
structure_name (str): name of the structure
Returns:
Structure
StructureContract
"""
return next((st for st in self.structures if st.name == structure_name), None)
def get_structure_from_canonical_name(self, structure_name: str) -> Optional["Structure"]:
def get_structure_from_canonical_name(
self, structure_name: str
) -> Optional["StructureContract"]:
"""
Return a structure from a canonical name
Args:
structure_name (str): canonical name of the structure
Returns:
Structure
StructureContract
"""
return next((st for st in self.structures if st.canonical_name == structure_name), None)
@ -853,7 +917,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:
@ -954,7 +1018,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)
@ -1207,7 +1273,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
###################################################################################
@ -1216,8 +1282,8 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
###################################################################################
def update_read_write_using_ssa(self):
for function in self.functions + self.modifiers:
def update_read_write_using_ssa(self) -> None:
for function in self.functions + list(self.modifiers):
function.update_read_write_using_ssa()
# endregion
@ -1252,7 +1318,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
@ -1281,7 +1347,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
@ -1289,7 +1355,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
@ -1308,10 +1374,10 @@ 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):
def add_constructor_variables(self) -> None:
from slither.core.declarations.function_contract import FunctionContract
if self.state_variables:
@ -1320,8 +1386,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
@ -1352,8 +1418,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
@ -1380,7 +1446,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def _create_node(
self, func: Function, counter: int, variable: "Variable", scope: Union[Scope, Function]
):
) -> "Node":
from slither.core.cfg.node import Node, NodeType
from slither.core.expressions import (
AssignmentOperationType,
@ -1412,7 +1478,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
###################################################################################
def convert_expression_to_slithir_ssa(self):
def convert_expression_to_slithir_ssa(self) -> None:
"""
Assume generate_slithir_and_analyze was called on all functions
@ -1434,22 +1500,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):
last_state_variables_instances = {}
initial_state_variables_instances = {}
def fix_phi(self) -> None:
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
@ -1459,20 +1526,20 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
###################################################################################
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
if isinstance(other, str):
return other == self.name
return NotImplemented
def __neq__(self, other):
def __neq__(self, other: Any) -> bool:
if isinstance(other, str):
return other != self.name
return NotImplemented
def __str__(self):
def __str__(self) -> str:
return self.name
def __hash__(self):
return self._id
def __hash__(self) -> int:
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
@ -9,7 +9,7 @@ if TYPE_CHECKING:
class CustomError(SourceMapping):
def __init__(self, compilation_unit: "SlitherCompilationUnit"):
def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None:
super().__init__()
self._name: str = ""
self._parameters: List[LocalVariable] = []
@ -30,7 +30,7 @@ class CustomError(SourceMapping):
def parameters(self) -> List[LocalVariable]:
return self._parameters
def add_parameters(self, p: "LocalVariable"):
def add_parameters(self, p: "LocalVariable") -> None:
self._parameters.append(p)
@property
@ -42,7 +42,7 @@ class CustomError(SourceMapping):
###################################################################################
@staticmethod
def _convert_type_for_solidity_signature(t: Optional[Union[Type, List[Type]]]):
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) + ")"
@ -92,5 +92,5 @@ class CustomError(SourceMapping):
###################################################################################
###################################################################################
def __str__(self):
def __str__(self) -> str:
return "revert " + self.solidity_signature

@ -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

@ -4,7 +4,7 @@ from slither.core.source_mapping.source_mapping import SourceMapping
class Enum(SourceMapping):
def __init__(self, name: str, canonical_name: str, values: List[str]):
def __init__(self, name: str, canonical_name: str, values: List[str]) -> None:
super().__init__()
self._name = name
self._canonical_name = canonical_name
@ -33,5 +33,5 @@ class Enum(SourceMapping):
def max(self) -> int:
return self._max
def __str__(self):
def __str__(self) -> str:
return self.name

@ -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

@ -8,6 +8,8 @@ if TYPE_CHECKING:
class EnumTopLevel(Enum, TopLevel):
def __init__(self, name: str, canonical_name: str, values: List[str], scope: "FileScope"):
def __init__(
self, name: str, canonical_name: str, values: List[str], scope: "FileScope"
) -> None:
super().__init__(name, canonical_name, values)
self.file_scope: "FileScope" = scope

@ -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,8 +8,8 @@ if TYPE_CHECKING:
from slither.core.declarations import Contract
class Event(ChildContract, SourceMapping):
def __init__(self):
class Event(ContractLevel, SourceMapping):
def __init__(self) -> None:
super().__init__()
self._name = None
self._elems: List[EventVariable] = []
@ -59,5 +59,5 @@ class Event(ChildContract, SourceMapping):
"""
return self.contract == contract
def __str__(self):
def __str__(self) -> str:
return self.name

@ -6,7 +6,7 @@ from abc import abstractmethod, ABCMeta
from collections import namedtuple
from enum import Enum
from itertools import groupby
from typing import Dict, TYPE_CHECKING, List, Optional, Set, Union, Callable, Tuple
from typing import Any, Dict, TYPE_CHECKING, List, Optional, Set, Union, Callable, Tuple
from slither.core.cfg.scope import Scope
from slither.core.declarations.solidity_variables import (
@ -27,6 +27,7 @@ from slither.core.variables.state_variable import StateVariable
from slither.utils.type import convert_type_for_solidity_signature_to_string
from slither.utils.utils import unroll
# pylint: disable=import-outside-toplevel,too-many-instance-attributes,too-many-statements,too-many-lines
if TYPE_CHECKING:
@ -45,6 +46,7 @@ if TYPE_CHECKING:
from slither.slithir.operations import Operation
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.scope.scope import FileScope
from slither.slithir.variables.state_variable import StateIRVariable
LOGGER = logging.getLogger("Function")
ReacheableNode = namedtuple("ReacheableNode", ["node", "ir"])
@ -56,7 +58,7 @@ class ModifierStatements:
modifier: Union["Contract", "Function"],
entry_point: "Node",
nodes: List["Node"],
):
) -> None:
self._modifier = modifier
self._entry_point = entry_point
self._nodes = nodes
@ -116,7 +118,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
Function class
"""
def __init__(self, compilation_unit: "SlitherCompilationUnit"):
def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None:
super().__init__()
self._internal_scope: List[str] = []
self._name: Optional[str] = None
@ -295,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=None) -> bool:
def can_reenter(self, callstack: Optional[List[Union["Function", "Variable"]]] = None) -> bool:
"""
Check if the function can re-enter
Follow internal calls.
@ -370,7 +372,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
###################################################################################
###################################################################################
def set_function_type(self, t: FunctionType):
def set_function_type(self, t: FunctionType) -> None:
assert isinstance(t, FunctionType)
self._function_type = t
@ -455,7 +457,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
def visibility(self, v: str):
self._visibility = v
def set_visibility(self, v: str):
def set_visibility(self, v: str) -> None:
self._visibility = v
@property
@ -554,7 +556,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
def entry_point(self, node: "Node"):
self._entry_point = node
def add_node(self, node: "Node"):
def add_node(self, node: "Node") -> None:
if not self._entry_point:
self._entry_point = node
self._nodes.append(node)
@ -598,7 +600,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
return list(self._parameters)
def add_parameters(self, p: "LocalVariable"):
def add_parameters(self, p: "LocalVariable") -> None:
self._parameters.append(p)
@property
@ -608,7 +610,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
return list(self._parameters_ssa)
def add_parameter_ssa(self, var: "LocalIRVariable"):
def add_parameter_ssa(self, var: "LocalIRVariable") -> None:
self._parameters_ssa.append(var)
def parameters_src(self) -> SourceMapping:
@ -651,7 +653,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
return list(self._returns)
def add_return(self, r: "LocalVariable"):
def add_return(self, r: "LocalVariable") -> None:
self._returns.append(r)
@property
@ -661,7 +663,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
return list(self._returns_ssa)
def add_return_ssa(self, var: "LocalIRVariable"):
def add_return_ssa(self, var: "LocalIRVariable") -> None:
self._returns_ssa.append(var)
# endregion
@ -680,7 +682,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
return [c.modifier for c in self._modifiers]
def add_modifier(self, modif: "ModifierStatements"):
def add_modifier(self, modif: "ModifierStatements") -> None:
self._modifiers.append(modif)
@property
@ -714,7 +716,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
# This is a list of contracts internally, so we convert it to a list of constructor functions.
return list(self._explicit_base_constructor_calls)
def add_explicit_base_constructor_calls_statements(self, modif: ModifierStatements):
def add_explicit_base_constructor_calls_statements(self, modif: ModifierStatements) -> None:
self._explicit_base_constructor_calls.append(modif)
# endregion
@ -1057,7 +1059,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self._all_reachable_from_functions = functions
return self._all_reachable_from_functions
def add_reachable_from_node(self, n: "Node", ir: "Operation"):
def add_reachable_from_node(self, n: "Node", ir: "Operation") -> None:
self._reachable_from_nodes.add(ReacheableNode(n, ir))
self._reachable_from_functions.add(n.function)
@ -1068,7 +1070,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
###################################################################################
###################################################################################
def _explore_functions(self, f_new_values: Callable[["Function"], List]):
def _explore_functions(self, f_new_values: Callable[["Function"], List]) -> List[Any]:
values = f_new_values(self)
explored = [self]
to_explore = [
@ -1218,11 +1220,13 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
func: "Function",
f: Callable[["Node"], List[SolidityVariable]],
include_loop: bool,
):
) -> List[Any]:
ret = [f(n) for n in func.nodes if n.is_conditional(include_loop)]
return [item for sublist in ret for item in sublist]
def all_conditional_solidity_variables_read(self, include_loop=True) -> List[SolidityVariable]:
def all_conditional_solidity_variables_read(
self, include_loop: bool = True
) -> List[SolidityVariable]:
"""
Return the Soldiity variables directly used in a condtion
@ -1258,7 +1262,9 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
return [var for var in ret if isinstance(var, SolidityVariable)]
@staticmethod
def _explore_func_nodes(func: "Function", f: Callable[["Node"], List[SolidityVariable]]):
def _explore_func_nodes(
func: "Function", f: Callable[["Node"], List[SolidityVariable]]
) -> List[Union[Any, SolidityVariableComposed]]:
ret = [f(n) for n in func.nodes]
return [item for sublist in ret for item in sublist]
@ -1367,7 +1373,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
with open(filename, "w", encoding="utf8") as f:
f.write(content)
def slithir_cfg_to_dot_str(self, skip_expressions=False) -> str:
def slithir_cfg_to_dot_str(self, skip_expressions: bool = False) -> str:
"""
Export the CFG to a DOT format. The nodes includes the Solidity expressions and the IRs
:return: the DOT content
@ -1378,7 +1384,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
content = ""
content += "digraph{\n"
for node in self.nodes:
label = f"Node Type: {str(node.type)} {node.node_id}\n"
label = f"Node Type: {node.type.value} {node.node_id}\n"
if node.expression and not skip_expressions:
label += f"\nEXPRESSION:\n{node.expression}\n"
if node.irs and not skip_expressions:
@ -1512,7 +1518,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
###################################################################################
###################################################################################
def _analyze_read_write(self):
def _analyze_read_write(self) -> None:
"""Compute variables read/written/..."""
write_var = [x.variables_written_as_expression for x in self.nodes]
write_var = [x for x in write_var if x]
@ -1570,7 +1576,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
slithir_variables = [x for x in slithir_variables if x]
self._slithir_variables = [item for sublist in slithir_variables for item in sublist]
def _analyze_calls(self):
def _analyze_calls(self) -> None:
calls = [x.calls_as_expression for x in self.nodes]
calls = [x for x in calls if x]
calls = [item for sublist in calls for item in sublist]
@ -1702,7 +1708,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
return self._get_last_ssa_variable_instances(target_state=False, target_local=True)
@staticmethod
def _unchange_phi(ir: "Operation"):
def _unchange_phi(ir: "Operation") -> bool:
from slither.slithir.operations import Phi, PhiCallback
if not isinstance(ir, (Phi, PhiCallback)) or len(ir.rvalues) > 1:
@ -1711,7 +1717,11 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
return True
return ir.rvalues[0] == ir.lvalue
def fix_phi(self, last_state_variables_instances, initial_state_variables_instances):
def fix_phi(
self,
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
@ -1745,7 +1755,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
node.irs_ssa = [ir for ir in node.irs_ssa if not self._unchange_phi(ir)]
def generate_slithir_and_analyze(self):
def generate_slithir_and_analyze(self) -> None:
for node in self.nodes:
node.slithir_generation()
@ -1756,7 +1766,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
def generate_slithir_ssa(self, all_ssa_state_variables_instances):
pass
def update_read_write_using_ssa(self):
def update_read_write_using_ssa(self) -> None:
for node in self.nodes:
node.update_read_write_using_ssa()
self._analyze_read_write()
@ -1767,7 +1777,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
###################################################################################
###################################################################################
def __str__(self):
def __str__(self) -> str:
return self.name
# endregion

@ -1,20 +1,44 @@
"""
Function module
"""
from typing import 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
# pylint: disable=import-outside-toplevel,too-many-instance-attributes,too-many-statements,too-many-lines
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, 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
class FunctionContract(Function, ChildContract, ChildInheritance):
@property
def canonical_name(self) -> str:
"""
@ -71,7 +95,7 @@ class FunctionContract(Function, ChildContract, ChildInheritance):
def get_summary(
self,
) -> Tuple[str, str, str, List[str], List[str], List[str], List[str], List[str]]:
) -> Tuple[str, str, str, List[str], List[str], List[str], List[str], List[str], int]:
"""
Return the function summary
Returns:
@ -87,6 +111,7 @@ class FunctionContract(Function, ChildContract, ChildInheritance):
[str(x) for x in self.state_variables_written],
[str(x) for x in self.internal_calls],
[str(x) for x in self.external_calls_as_expressions],
compute_cyclomatic_complexity(self),
)
# endregion
@ -96,7 +121,9 @@ class FunctionContract(Function, ChildContract, ChildInheritance):
###################################################################################
###################################################################################
def generate_slithir_ssa(self, all_ssa_state_variables_instances):
def generate_slithir_ssa(
self, all_ssa_state_variables_instances: Dict[str, "StateIRVariable"]
) -> None:
from slither.slithir.utils.ssa import add_ssa_ir, transform_slithir_vars_to_ssa
from slither.core.dominators.utils import (
compute_dominance_frontier,

@ -1,7 +1,7 @@
"""
Function module
"""
from typing import List, Tuple, TYPE_CHECKING
from typing import Dict, List, Tuple, TYPE_CHECKING
from slither.core.declarations import Function
from slither.core.declarations.top_level import TopLevel
@ -9,10 +9,11 @@ from slither.core.declarations.top_level import TopLevel
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
class FunctionTopLevel(Function, TopLevel):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope"):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope") -> None:
super().__init__(compilation_unit)
self._scope: "FileScope" = scope
@ -78,7 +79,9 @@ class FunctionTopLevel(Function, TopLevel):
###################################################################################
###################################################################################
def generate_slithir_ssa(self, all_ssa_state_variables_instances):
def generate_slithir_ssa(
self, all_ssa_state_variables_instances: Dict[str, "StateIRVariable"]
) -> None:
# pylint: disable=import-outside-toplevel
from slither.slithir.utils.ssa import add_ssa_ir, transform_slithir_vars_to_ssa
from slither.core.dominators.utils import (

@ -8,7 +8,7 @@ if TYPE_CHECKING:
class Import(SourceMapping):
def __init__(self, filename: Path, scope: "FileScope"):
def __init__(self, filename: Path, scope: "FileScope") -> None:
super().__init__()
self._filename: Path = filename
self._alias: Optional[str] = None

@ -7,7 +7,7 @@ if TYPE_CHECKING:
class Pragma(SourceMapping):
def __init__(self, directive: List[str], scope: "FileScope"):
def __init__(self, directive: List[str], scope: "FileScope") -> None:
super().__init__()
self._directive = directive
self.scope: "FileScope" = scope
@ -39,5 +39,5 @@ class Pragma(SourceMapping):
return self._directive[0] == "experimental" and self._directive[1] == "ABIEncoderV2"
return False
def __str__(self):
def __str__(self) -> str:
return "pragma " + "".join(self.directive)

@ -1,7 +1,11 @@
"""
Special variable to model import with renaming
"""
from typing import Union
from slither.core.declarations import Import
from slither.core.declarations.contract import Contract
from slither.core.declarations.solidity_variables import SolidityVariable
from slither.core.solidity_types import ElementaryType
from slither.core.variables.variable import Variable
@ -13,7 +17,7 @@ class SolidityImportPlaceHolder(Variable):
In the long term we should remove this and better integrate import aliases
"""
def __init__(self, import_directive: Import):
def __init__(self, import_directive: Import) -> None:
super().__init__()
assert import_directive.alias is not None
self._import_directive = import_directive
@ -27,7 +31,7 @@ class SolidityImportPlaceHolder(Variable):
def type(self) -> ElementaryType:
return ElementaryType("string")
def __eq__(self, other):
def __eq__(self, other: Union[Contract, SolidityVariable]) -> bool:
return (
self.__class__ == other.__class__
and self._import_directive.filename == self._import_directive.filename

@ -1,13 +1,11 @@
# https://solidity.readthedocs.io/en/v0.4.24/units-and-global-variables.html
from typing import List, Dict, Union, TYPE_CHECKING
from typing import List, Dict, Union, Any
from slither.core.declarations.custom_error import CustomError
from slither.core.solidity_types import ElementaryType, TypeInformation
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.exceptions import SlitherException
if TYPE_CHECKING:
pass
SOLIDITY_VARIABLES = {
"now": "uint256",
@ -84,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
@ -98,17 +96,17 @@ def solidity_function_signature(name):
class SolidityVariable(SourceMapping):
def __init__(self, name: str):
def __init__(self, name: str) -> None:
super().__init__()
self._check_name(name)
self._name = name
# dev function, will be removed once the code is stable
def _check_name(self, name: str): # pylint: disable=no-self-use
def _check_name(self, name: str) -> None: # pylint: disable=no-self-use
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"):
@ -124,18 +122,18 @@ class SolidityVariable(SourceMapping):
def type(self) -> ElementaryType:
return ElementaryType(SOLIDITY_VARIABLES[self.name])
def __str__(self):
def __str__(self) -> str:
return self._name
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
return self.__class__ == other.__class__ and self.name == other.name
def __hash__(self):
def __hash__(self) -> int:
return hash(self.name)
class SolidityVariableComposed(SolidityVariable):
def _check_name(self, name: str):
def _check_name(self, name: str) -> None:
assert name in SOLIDITY_VARIABLES_COMPOSED
@property
@ -146,13 +144,13 @@ class SolidityVariableComposed(SolidityVariable):
def type(self) -> ElementaryType:
return ElementaryType(SOLIDITY_VARIABLES_COMPOSED[self.name])
def __str__(self):
def __str__(self) -> str:
return self._name
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
return self.__class__ == other.__class__ and self.name == other.name
def __hash__(self):
def __hash__(self) -> int:
return hash(self.name)
@ -162,7 +160,7 @@ class SolidityFunction(SourceMapping):
# https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#type-information
# As a result, we set return_type during the Ir conversion
def __init__(self, name: str):
def __init__(self, name: str) -> None:
super().__init__()
assert name in SOLIDITY_FUNCTIONS
self._name = name
@ -184,31 +182,31 @@ 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):
def __str__(self) -> str:
return self._name
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
return self.__class__ == other.__class__ and self.name == other.name
def __hash__(self):
def __hash__(self) -> int:
return hash(self.name)
class SolidityCustomRevert(SolidityFunction):
def __init__(self, custom_error: CustomError): # pylint: disable=super-init-not-called
def __init__(self, custom_error: CustomError) -> None: # pylint: disable=super-init-not-called
self._name = "revert " + custom_error.solidity_signature
self._custom_error = custom_error
self._return_type: List[Union[TypeInformation, ElementaryType]] = []
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
return (
self.__class__ == other.__class__
and self.name == other.name
and self._custom_error == other._custom_error
)
def __hash__(self):
def __hash__(self) -> int:
return hash(hash(self.name) + hash(self._custom_error))

@ -51,3 +51,17 @@ class Structure(SourceMapping):
def __str__(self) -> str:
return self.name
def __eq__(self, other) -> bool:
if not isinstance(other, Structure):
return False
if len(self.elems) != len(other.elems):
return False
for idx, elem in enumerate(self.elems_ordered):
other_elem = other.elems_ordered[idx]
if str(other_elem.type) != str(elem.type) or other_elem.name != elem.name:
return False
return self.name == other.name
def __hash__(self):
return hash(self.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

@ -9,6 +9,6 @@ if TYPE_CHECKING:
class StructureTopLevel(Structure, 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

@ -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
@ -8,11 +9,11 @@ if TYPE_CHECKING:
class UsingForTopLevel(TopLevel):
def __init__(self, scope: "FileScope"):
def __init__(self, scope: "FileScope") -> None:
super().__init__()
self._using_for: Dict[Union[str, Type], List[Type]] = {}
self.file_scope: "FileScope" = scope
@property
def using_for(self) -> Dict[Type, List[Type]]:
def using_for(self) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
return self._using_for

@ -1,4 +1,4 @@
from typing import List, TYPE_CHECKING
from typing import Set, List, TYPE_CHECKING
from slither.core.cfg.node import NodeType
@ -6,7 +6,7 @@ if TYPE_CHECKING:
from slither.core.cfg.node import Node
def intersection_predecessor(node: "Node"):
def intersection_predecessor(node: "Node") -> Set["Node"]:
if not node.fathers:
return set()
ret = node.fathers[0].dominators
@ -15,7 +15,7 @@ def intersection_predecessor(node: "Node"):
return ret
def _compute_dominators(nodes: List["Node"]):
def _compute_dominators(nodes: List["Node"]) -> None:
changed = True
while changed:
@ -28,7 +28,7 @@ def _compute_dominators(nodes: List["Node"]):
changed = True
def _compute_immediate_dominators(nodes: List["Node"]):
def _compute_immediate_dominators(nodes: List["Node"]) -> None:
for node in nodes:
idom_candidates = set(node.dominators)
idom_candidates.remove(node)
@ -58,7 +58,7 @@ def _compute_immediate_dominators(nodes: List["Node"]):
idom.dominator_successors.add(node)
def compute_dominators(nodes: List["Node"]):
def compute_dominators(nodes: List["Node"]) -> None:
"""
Naive implementation of Cooper, Harvey, Kennedy algo
See 'A Simple,Fast Dominance Algorithm'
@ -74,7 +74,7 @@ def compute_dominators(nodes: List["Node"]):
_compute_immediate_dominators(nodes)
def compute_dominance_frontier(nodes: List["Node"]):
def compute_dominance_frontier(nodes: List["Node"]) -> None:
"""
Naive implementation of Cooper, Harvey, Kennedy algo
See 'A Simple,Fast Dominance Algorithm'
@ -95,4 +95,5 @@ def compute_dominance_frontier(nodes: List["Node"]):
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,20 +77,20 @@ class AssignmentOperationType(Enum):
raise SlitherCoreError(f"str: Unknown operation type {self})")
class AssignmentOperation(ExpressionTyped):
class AssignmentOperation(Expression):
def __init__(
self,
left_expression: Expression,
right_expression: Expression,
expression_type: AssignmentOperationType,
expression_return_type: Optional["Type"],
):
) -> None:
assert isinstance(left_expression, Expression)
assert isinstance(right_expression, Expression)
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,

@ -1,10 +1,10 @@
from typing import Optional, List
from typing import Any, Optional, List
from slither.core.expressions.expression import Expression
class CallExpression(Expression): # pylint: disable=too-many-instance-attributes
def __init__(self, called, arguments, type_call):
def __init__(self, called: Expression, arguments: List[Any], type_call: str) -> None:
assert isinstance(called, Expression)
super().__init__()
self._called: Expression = called
@ -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
@ -53,7 +53,7 @@ class CallExpression(Expression): # pylint: disable=too-many-instance-attribute
def type_call(self) -> str:
return self._type_call
def __str__(self):
def __str__(self) -> str:
txt = str(self._called)
if self.call_gas or self.call_value:
gas = f"gas: {self.call_gas}" if self.call_gas else ""

@ -1,10 +1,23 @@
from typing import List
from typing import Union, List
from .expression import Expression
from slither.core.expressions.binary_operation import BinaryOperation
from slither.core.expressions.expression import Expression
from slither.core.expressions.identifier import Identifier
from slither.core.expressions.literal import Literal
from slither.core.expressions.tuple_expression import TupleExpression
from slither.core.expressions.type_conversion import TypeConversion
from slither.core.expressions.unary_operation import UnaryOperation
class ConditionalExpression(Expression):
def __init__(self, if_expression, then_expression, else_expression):
def __init__(
self,
if_expression: Union[BinaryOperation, Identifier, Literal],
then_expression: Union[
"ConditionalExpression", TypeConversion, Literal, TupleExpression, Identifier
],
else_expression: Union[TupleExpression, UnaryOperation, Identifier, Literal],
) -> None:
assert isinstance(if_expression, Expression)
assert isinstance(then_expression, Expression)
assert isinstance(else_expression, Expression)
@ -29,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)

@ -3,10 +3,11 @@
"""
from slither.core.expressions.expression import Expression
from slither.core.solidity_types.type import Type
from slither.core.solidity_types.elementary_type import ElementaryType
class ElementaryTypeNameExpression(Expression):
def __init__(self, t):
def __init__(self, t: ElementaryType) -> None:
assert isinstance(t, Type)
super().__init__()
self._type = t
@ -20,5 +21,5 @@ class ElementaryTypeNameExpression(Expression):
assert isinstance(new_type, Type)
self._type = new_type
def __str__(self):
def __str__(self) -> str:
return str(self._type)

@ -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,20 +1,18 @@
from typing import 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):
def __init__(self, left_expression, right_expression, index_type):
class IndexAccess(Expression):
def __init__(
self,
left_expression: Union["IndexAccess", Identifier],
right_expression: Union[Literal, Identifier],
) -> 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"]:
@ -28,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):
def __str__(self) -> str:
return str(self.expression_left) + "[" + str(self.expression_right) + "]"

@ -48,7 +48,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,11 +1,10 @@
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):
def __init__(self, member_name, member_type, expression):
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
assert isinstance(expression, Expression)
@ -26,5 +25,5 @@ class MemberAccess(ExpressionTyped):
def type(self) -> Type:
return self._type
def __str__(self):
def __str__(self) -> str:
return str(self.expression) + "." + self.member_name

@ -1,11 +1,20 @@
from typing import Union, TYPE_CHECKING
from slither.core.expressions.expression import Expression
from slither.core.solidity_types.type import Type
if TYPE_CHECKING:
from slither.core.solidity_types.elementary_type import ElementaryType
from slither.core.solidity_types.type_alias import TypeAliasTopLevel
class NewArray(Expression):
# note: dont conserve the size of the array if provided
def __init__(self, depth, array_type):
def __init__(
self, depth: int, array_type: Union["TypeAliasTopLevel", "ElementaryType"]
) -> None:
super().__init__()
assert isinstance(array_type, Type)
self._depth: int = depth

@ -2,7 +2,7 @@ from slither.core.expressions.expression import Expression
class NewContract(Expression):
def __init__(self, contract_name):
def __init__(self, contract_name: str) -> None:
super().__init__()
self._contract_name: str = contract_name
self._gas = None
@ -29,5 +29,5 @@ class NewContract(Expression):
def call_salt(self, salt):
self._salt = salt
def __str__(self):
def __str__(self) -> str:
return "new " + str(self._contract_name)

@ -1,19 +1,43 @@
from slither.core.expressions.expression_typed import ExpressionTyped
from typing import Union, TYPE_CHECKING
from slither.core.expressions.expression import Expression
from slither.core.solidity_types.type import Type
if TYPE_CHECKING:
from slither.core.expressions.call_expression import CallExpression
from slither.core.expressions.identifier import Identifier
from slither.core.expressions.literal import Literal
from slither.core.expressions.member_access import MemberAccess
from slither.core.solidity_types.elementary_type import ElementaryType
from slither.core.solidity_types.type_alias import TypeAliasContract
from slither.core.solidity_types.user_defined_type import UserDefinedType
class TypeConversion(ExpressionTyped):
def __init__(self, expression, expression_type):
class TypeConversion(Expression):
def __init__(
self,
expression: Union[
"MemberAccess", "Literal", "CallExpression", "TypeConversion", "Identifier"
],
expression_type: Union["ElementaryType", "UserDefinedType", "TypeAliasContract"],
) -> None:
super().__init__()
assert isinstance(expression, Expression)
assert isinstance(expression_type, Type)
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
def __str__(self):
def __str__(self) -> str:
return str(self.type) + "(" + str(self.expression) + ")"

@ -1,9 +1,14 @@
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
from slither.core.expressions.index_access import IndexAccess
from slither.core.expressions.literal import Literal
from slither.core.expressions.tuple_expression import TupleExpression
logger = logging.getLogger("UnaryOperation")
@ -20,7 +25,7 @@ class UnaryOperationType(Enum):
MINUS_PRE = 8 # for stuff like uint(-1)
@staticmethod
def get_type(operation_type, isprefix):
def get_type(operation_type: str, isprefix: bool) -> "UnaryOperationType":
if isprefix:
if operation_type == "!":
return UnaryOperationType.BANG
@ -43,7 +48,7 @@ class UnaryOperationType(Enum):
return UnaryOperationType.MINUSMINUS_POST
raise SlitherCoreError(f"get_type: Unknown operation type {operation_type}")
def __str__(self):
def __str__(self) -> str:
if self == UnaryOperationType.BANG:
return "!"
if self == UnaryOperationType.TILD:
@ -65,7 +70,7 @@ class UnaryOperationType(Enum):
raise SlitherCoreError(f"str: Unknown operation type {self}")
@staticmethod
def is_prefix(operation_type):
def is_prefix(operation_type: "UnaryOperationType") -> bool:
if operation_type in [
UnaryOperationType.BANG,
UnaryOperationType.TILD,
@ -85,8 +90,12 @@ class UnaryOperationType(Enum):
raise SlitherCoreError(f"is_prefix: Unknown operation type {operation_type}")
class UnaryOperation(ExpressionTyped):
def __init__(self, expression, expression_type):
class UnaryOperation(Expression):
def __init__(
self,
expression: Union[Literal, Identifier, IndexAccess, TupleExpression],
expression_type: UnaryOperationType,
) -> None:
assert isinstance(expression, Expression)
super().__init__()
self._expression: Expression = expression
@ -114,7 +123,7 @@ class UnaryOperation(ExpressionTyped):
def is_prefix(self) -> bool:
return UnaryOperationType.is_prefix(self._type)
def __str__(self):
def __str__(self) -> str:
if self.is_prefix:
return str(self.type) + " " + str(self._expression)
return str(self._expression) + " " + str(self.type)

@ -25,7 +25,7 @@ def _dict_contain(d1: Dict, d2: Dict) -> bool:
# pylint: disable=too-many-instance-attributes
class FileScope:
def __init__(self, filename: Filename):
def __init__(self, filename: Filename) -> None:
self.filename = filename
self.accessible_scopes: List[FileScope] = []

@ -8,12 +8,12 @@ import pathlib
import posixpath
import re
from collections import defaultdict
from typing import Optional, Dict, List, Set, Union
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
@ -40,7 +40,7 @@ class SlitherCore(Context):
Slither static analyzer
"""
def __init__(self):
def __init__(self) -> None:
super().__init__()
self._filename: Optional[str] = None
@ -73,8 +73,8 @@ class SlitherCore(Context):
# Maps from file to detector name to the start/end ranges for that detector.
# Infinity is used to signal a detector has no end range.
self._ignore_ranges: defaultdict[str, defaultdict[str, List[(int, int)]]] = defaultdict(
lambda: defaultdict(lambda: [])
self._ignore_ranges: Dict[str, Dict[str, List[Tuple[int, ...]]]] = defaultdict(
lambda: defaultdict(lambda: [(-1, -1)])
)
self._compilation_units: List[SlitherCompilationUnit] = []
@ -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)
@ -443,7 +443,7 @@ class SlitherCore(Context):
return True
def load_previous_results(self):
def load_previous_results(self) -> None:
filename = self._previous_results_filename
try:
if os.path.isfile(filename):
@ -456,7 +456,7 @@ class SlitherCore(Context):
except json.decoder.JSONDecodeError:
logger.error(red(f"Impossible to decode {filename}. Consider removing the file"))
def write_results_to_hide(self):
def write_results_to_hide(self) -> None:
if not self._results_to_hide:
return
filename = self._previous_results_filename
@ -464,7 +464,7 @@ class SlitherCore(Context):
results = self._results_to_hide + self._previous_results
json.dump(results, f)
def save_results_to_hide(self, results: List[Dict]):
def save_results_to_hide(self, results: List[Dict]) -> None:
self._results_to_hide += results
def add_path_to_filter(self, path: str):
@ -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,27 +1,37 @@
from typing import Optional, Tuple
from typing import Union, Optional, Tuple, Any, TYPE_CHECKING
from slither.core.expressions import Literal
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
if TYPE_CHECKING:
from slither.core.expressions.binary_operation import BinaryOperation
from slither.core.expressions.identifier import Identifier
class ArrayType(Type):
def __init__(self, t, length):
def __init__(
self,
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
@ -56,15 +66,15 @@ class ArrayType(Type):
return elem_size * int(str(self._length_value)), True
return 32, True
def __str__(self):
def __str__(self) -> str:
if self._length:
return str(self._type) + f"[{str(self._length_value)}]"
return str(self._type) + "[]"
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
if not isinstance(other, ArrayType):
return False
return self._type == other.type and self.length == other.length
def __hash__(self):
def __hash__(self) -> int:
return hash(str(self))

@ -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
@ -216,13 +216,13 @@ class ElementaryType(Type):
return MaxValues[self.name]
raise SlitherException(f"{self.name} does not have a max value")
def __str__(self):
def __str__(self) -> str:
return self._type
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
if not isinstance(other, ElementaryType):
return False
return self.type == other.type
def __hash__(self):
def __hash__(self) -> int:
return hash(str(self))

@ -1,4 +1,4 @@
from typing import List, Tuple
from typing import List, Tuple, Any
from slither.core.solidity_types.type import Type
from slither.core.variables.function_type_variable import FunctionTypeVariable
@ -9,7 +9,7 @@ class FunctionType(Type):
self,
params: List[FunctionTypeVariable],
return_values: List[FunctionTypeVariable],
):
) -> None:
assert all(isinstance(x, FunctionTypeVariable) for x in params)
assert all(isinstance(x, FunctionTypeVariable) for x in return_values)
super().__init__()
@ -68,7 +68,7 @@ class FunctionType(Type):
return f"({params}) returns({return_values})"
return f"({params})"
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
if not isinstance(other, FunctionType):
return False
return self.params == other.params and self.return_values == other.return_values

@ -1,10 +1,18 @@
from typing import Tuple
from typing import Union, Tuple, TYPE_CHECKING, Any
from slither.core.solidity_types.type import Type
if TYPE_CHECKING:
from slither.core.solidity_types.elementary_type import ElementaryType
from slither.core.solidity_types.type_alias import TypeAliasTopLevel
class MappingType(Type):
def __init__(self, type_from, type_to):
def __init__(
self,
type_from: "ElementaryType",
type_to: Union["MappingType", "TypeAliasTopLevel", "ElementaryType"],
) -> None:
assert isinstance(type_from, Type)
assert isinstance(type_to, Type)
super().__init__()
@ -27,13 +35,13 @@ class MappingType(Type):
def is_dynamic(self) -> bool:
return True
def __str__(self):
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
def __hash__(self):
def __hash__(self) -> int:
return hash(str(self))

@ -1,8 +1,8 @@
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.solidity_types import Type
from slither.core.declarations.contract_level import ContractLevel
from slither.core.solidity_types import Type, ElementaryType
if TYPE_CHECKING:
from slither.core.declarations import Contract
@ -10,13 +10,13 @@ if TYPE_CHECKING:
class TypeAlias(Type):
def __init__(self, underlying_type: Type, name: str):
def __init__(self, underlying_type: ElementaryType, name: str) -> None:
super().__init__()
self.name = name
self.underlying_type = underlying_type
@property
def type(self) -> Type:
def type(self) -> ElementaryType:
"""
Return the underlying type. Alias for underlying_type
@ -31,7 +31,7 @@ class TypeAlias(Type):
def storage_size(self) -> Tuple[int, bool]:
return self.underlying_type.storage_size
def __hash__(self):
def __hash__(self) -> int:
return hash(str(self))
@property
@ -40,18 +40,18 @@ class TypeAlias(Type):
class TypeAliasTopLevel(TypeAlias, TopLevel):
def __init__(self, underlying_type: Type, name: str, scope: "FileScope"):
def __init__(self, underlying_type: ElementaryType, name: str, scope: "FileScope") -> None:
super().__init__(underlying_type, name)
self.file_scope: "FileScope" = scope
def __str__(self):
def __str__(self) -> str:
return self.name
class TypeAliasContract(TypeAlias, ChildContract):
def __init__(self, underlying_type: Type, name: str, contract: "Contract"):
class TypeAliasContract(TypeAlias, ContractLevel):
def __init__(self, underlying_type: ElementaryType, name: str, contract: "Contract") -> None:
super().__init__(underlying_type, name)
self._contract: "Contract" = contract
def __str__(self):
def __str__(self) -> str:
return self.contract.name + "." + self.name

@ -1,16 +1,17 @@
from typing import 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
if TYPE_CHECKING:
from slither.core.declarations.contract import Contract
from slither.core.declarations.enum import Enum
# Use to model the Type(X) function, which returns an undefined type
# https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#type-information
class TypeInformation(Type):
def __init__(self, c):
def __init__(self, c: Union[ElementaryType, "Contract", "Enum"]) -> None:
# pylint: disable=import-outside-toplevel
from slither.core.declarations.contract import Contract
from slither.core.declarations.enum import Enum
@ -20,7 +21,7 @@ class TypeInformation(Type):
self._type = c
@property
def type(self) -> "Contract":
def type(self) -> Union["Contract", ElementaryType, "Enum"]:
return self._type
@property
@ -39,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,4 +1,4 @@
from typing import Union, TYPE_CHECKING, Tuple
from typing import Union, TYPE_CHECKING, Tuple, Any
import math
from slither.core.solidity_types.type import Type
@ -11,7 +11,7 @@ if TYPE_CHECKING:
# pylint: disable=import-outside-toplevel
class UserDefinedType(Type):
def __init__(self, t):
def __init__(self, t: Union["Enum", "Contract", "Structure"]) -> None:
from slither.core.declarations.structure import Structure
from slither.core.declarations.enum import Enum
from slither.core.declarations.contract import Contract
@ -62,7 +62,7 @@ class UserDefinedType(Type):
to_log = f"{self} does not have storage size"
raise SlitherException(to_log)
def __str__(self):
def __str__(self) -> str:
from slither.core.declarations.structure_contract import StructureContract
from slither.core.declarations.enum_contract import EnumContract
@ -71,10 +71,14 @@ class UserDefinedType(Type):
return str(type_used.contract) + "." + str(type_used.name)
return str(type_used.name)
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
from slither.core.declarations.contract import Contract
if not isinstance(other, UserDefinedType):
return False
if isinstance(self.type, Contract) and isinstance(other.type, Contract):
return self.type == other.type.name
return self.type == other.type
def __hash__(self):
def __hash__(self) -> int:
return hash(str(self))

@ -1,9 +1,9 @@
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
from slither.core.context.context import Context
if TYPE_CHECKING:
@ -18,7 +18,7 @@ if TYPE_CHECKING:
# pylint: disable=too-many-instance-attributes
class Source:
def __init__(self) -> None:
def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None:
self.start: int = 0
self.length: int = 0
self.filename: Filename = Filename("", "", "", "")
@ -27,7 +27,7 @@ class Source:
self.starting_column: int = 0
self.ending_column: int = 0
self.end: int = 0
self.compilation_unit: Optional["SlitherCompilationUnit"] = None
self.compilation_unit = compilation_unit
def to_json(self) -> Dict:
return {
@ -51,37 +51,57 @@ class Source:
filename_relative: str = self.filename.relative if self.filename.relative else ""
return f"{markdown_root}{filename_relative}{lines}"
def to_detailled_str(self) -> str:
def to_detailed_str(self) -> str:
lines = self._get_lines_str()
filename_short: str = self.filename.short if self.filename.short else ""
return f"{filename_short}{lines} ({self.starting_column} - {self.ending_column})"
def _get_lines_str(self, line_descr=""):
# If the compilation unit was not initialized, it means that the set_offset was never called
# on the corresponding object, which should not happen
assert self.compilation_unit is not None
def _get_lines_str(self, line_descr: str = "") -> str:
line_prefix = self.compilation_unit.core.line_prefix
lines = self.lines
if not lines:
lines = ""
elif len(lines) == 1:
lines = f"{line_prefix}{line_descr}{lines[0]}"
else:
lines = f"{line_prefix}{line_descr}{lines[0]}-{line_descr}{lines[-1]}"
return lines
return ""
if len(lines) == 1:
return f"{line_prefix}{line_descr}{lines[0]}"
return f"{line_prefix}{line_descr}{lines[0]}-{line_descr}{lines[-1]}"
@property
def content(self) -> str:
"""
Return the txt content of the Source
Returns:
"""
# If the compilation unit was not initialized, it means that the set_offset was never called
# on the corresponding object, which should not happen
assert self.compilation_unit
return self.compilation_unit.core.source_code[self.filename.absolute][self.start : self.end]
@property
def content_hash(self) -> str:
"""
Return sha1(self.content)
Returns:
"""
h = SHA1.new()
h.update(self.content.encode("utf8"))
return h.hexdigest()
def __str__(self) -> str:
lines = self._get_lines_str()
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 (
@ -105,6 +125,7 @@ def _compute_line(
Not done in an efficient way
"""
start_line, starting_column = compilation_unit.core.crytic_compile.get_line_from_offset(
filename, start
)
@ -127,7 +148,7 @@ def _convert_source_mapping(
position = re.findall("([0-9]*):([0-9]*):([-]?[0-9]*)", offset)
if len(position) != 1:
return Source()
return Source(compilation_unit)
s, l, f = position[0]
s = int(s)
@ -135,7 +156,7 @@ def _convert_source_mapping(
f = int(f)
if f not in sourceUnits:
new_source = Source()
new_source = Source(compilation_unit)
new_source.start = s
new_source.length = l
return new_source
@ -149,7 +170,7 @@ def _convert_source_mapping(
(lines, starting_column, ending_column) = _compute_line(compilation_unit, filename, s, l)
new_source = Source()
new_source = Source(compilation_unit)
new_source.start = s
new_source.length = l
new_source.filename = filename
@ -158,28 +179,22 @@ def _convert_source_mapping(
new_source.starting_column = starting_column
new_source.ending_column = ending_column
new_source.end = new_source.start + l
return new_source
class SourceMapping(Context, metaclass=ABCMeta):
def __init__(self) -> None:
super().__init__()
# self._source_mapping: Optional[Dict] = None
self.source_mapping: Source = Source()
self.source_mapping: Optional[Source] = None
self.references: List[Source] = []
def set_offset(
self, offset: Union["Source", str], compilation_unit: "SlitherCompilationUnit"
) -> None:
assert compilation_unit
if isinstance(offset, Source):
self.source_mapping.start = offset.start
self.source_mapping.length = offset.length
self.source_mapping.filename = offset.filename
self.source_mapping.is_dependency = offset.is_dependency
self.source_mapping.lines = offset.lines
self.source_mapping.starting_column = offset.starting_column
self.source_mapping.ending_column = offset.ending_column
self.source_mapping.end = offset.end
self.source_mapping = offset
else:
self.source_mapping = _convert_source_mapping(offset, compilation_unit)
self.source_mapping.compilation_unit = compilation_unit

@ -0,0 +1,2 @@
from .state_variable import StateVariable
from .variable import Variable

@ -1,9 +1,8 @@
from slither.core.variables.variable import Variable
from slither.core.children.child_event import ChildEvent
class EventVariable(ChildEvent, Variable):
def __init__(self):
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

@ -13,7 +13,7 @@ class LocalVariableInitFromTuple(LocalVariable):
"""
def __init__(self):
def __init__(self) -> None:
super().__init__()
self._tuple_index: Optional[int] = None

@ -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,8 +8,8 @@ if TYPE_CHECKING:
from slither.core.declarations import Contract
class StateVariable(ChildContract, Variable):
def __init__(self):
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

@ -9,7 +9,7 @@ if TYPE_CHECKING:
class TopLevelVariable(TopLevel, Variable):
def __init__(self, scope: "FileScope"):
def __init__(self, scope: "FileScope") -> None:
super().__init__()
self._node_initialization: Optional["Node"] = None
self.file_scope = scope

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

Loading…
Cancel
Save