diff --git a/.github/DISCUSSION_TEMPLATE/trouble_with_installation.yml b/.github/DISCUSSION_TEMPLATE/trouble_with_installation.yml new file mode 100644 index 0000000000..a2b56336c4 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/trouble_with_installation.yml @@ -0,0 +1,55 @@ +--- +body: + - + attributes: + value: | + Please check the issues tab to avoid duplicates. + Thanks for taking the time to fill out this bug report! + type: markdown + - + attributes: + label: "What operating system are you using?" + id: os + type: textarea + validations: + required: true + - + attributes: + label: "How did you install slither?" + description: | + For example, using git or python's pip. + id: install-method + type: textarea + validations: + required: true + - type: dropdown + id: python + attributes: + label: Do you have python added to your $PATH? + multiple: true + options: + - "Yes" + - "No" + - "Not sure" + - type: dropdown + id: solc + attributes: + label: Do you have solc-select installed? + multiple: true + options: + - "Yes" + - "No" + - + attributes: + description: | + Please copy and paste any relevant log output. This + will be automatically formatted into code, so no need for backticks. + render: shell + label: "Output of running `slither-doctor .`:" + id: logs + type: textarea +description: "Get help troubleshooting slither installation" +labels: + - installation-help +name: "Trouble with Installing Slither" +title: "[Installation-Help]: " diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index e31d615127..df9dd7da2c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -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 - diff --git a/.github/ISSUE_TEMPLATE/false_negative.yml b/.github/ISSUE_TEMPLATE/false_negative.yml new file mode 100644 index 0000000000..e11b6ca8d7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/false_negative.yml @@ -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]: " diff --git a/.github/ISSUE_TEMPLATE/false_positive.yml b/.github/ISSUE_TEMPLATE/false_positive.yml new file mode 100644 index 0000000000..258a70dfb6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/false_positive.yml @@ -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]: " diff --git a/.github/workflows/IR.yml b/.github/workflows/IR.yml index 891de2bfbb..02a264f8ed 100644 --- a/.github/workflows/IR.yml +++ b/.github/workflows/IR.yml @@ -13,6 +13,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: build: name: IR tests @@ -24,7 +28,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python 3.8 uses: actions/setup-python@v3 diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml index b805c4d9dc..1730f43347 100644 --- a/.github/workflows/black.yml +++ b/.github/workflows/black.yml @@ -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 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9913e487dd..50877e2623 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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,7 +51,7 @@ 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: @@ -63,7 +67,7 @@ jobs: - 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' diff --git a/.github/workflows/detectors.yml b/.github/workflows/detectors.yml index 15aa8a5dd9..05e81275dd 100644 --- a/.github/workflows/detectors.yml +++ b/.github/workflows/detectors.yml @@ -13,6 +13,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: build: name: Detectors tests @@ -24,7 +28,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python 3.8 uses: actions/setup-python@v3 @@ -38,4 +42,4 @@ jobs: solc-select use 0.7.3 --always-install - name: Test with pytest run: | - pytest tests/test_detectors.py + pytest tests/test_detectors.py diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 76c18379e5..7e6f88f1e0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -8,6 +8,10 @@ on: tags: - '*' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: docker: runs-on: ubuntu-latest diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000000..ba275117eb --- /dev/null +++ b/.github/workflows/docs.yml @@ -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: github-pages + 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 ".[dev]" + - run: pdoc -o docs/ slither '!slither.tools' #TODO fix import errors on pdoc run + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + # Upload the doc + path: 'docs/' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 diff --git a/.github/workflows/doctor.yml b/.github/workflows/doctor.yml index b6124216a1..85d79f214f 100644 --- a/.github/workflows/doctor.yml +++ b/.github/workflows/doctor.yml @@ -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 }} diff --git a/.github/workflows/features.yml b/.github/workflows/features.yml index 5007fd7bf7..2c112e0aa7 100644 --- a/.github/workflows/features.yml +++ b/.github/workflows/features.yml @@ -13,6 +13,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: build: name: Features tests @@ -24,7 +28,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python 3.8 uses: actions/setup-python@v3 @@ -46,6 +50,7 @@ jobs: pytest tests/test_features.py pytest tests/test_constant_folding.py pytest tests/slithir/test_ternary_expressions.py + pytest tests/slithir/test_operation_reads.py pytest tests/test_functions_ids.py pytest tests/test_function.py pytest tests/test_source_mapping.py diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 5d9ff90176..bc9177802b 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -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) diff --git a/.github/workflows/parser.yml b/.github/workflows/parser.yml index 5e11420cfd..7b2a9efc83 100644 --- a/.github/workflows/parser.yml +++ b/.github/workflows/parser.yml @@ -13,6 +13,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: build: name: Parser tests @@ -24,7 +28,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Python 3.8 uses: actions/setup-python@v3 @@ -42,4 +46,4 @@ jobs: - name: Test with pytest run: | - pytest tests/test_ast_parsing.py + pytest tests/test_ast_parsing.py -n auto diff --git a/.github/workflows/pip-audit.yml b/.github/workflows/pip-audit.yml index 6360930711..e334ff4feb 100644 --- a/.github/workflows/pip-audit.yml +++ b/.github/workflows/pip-audit.yml @@ -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 diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 04334a2d53..06958a10d3 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -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 diff --git a/.github/workflows/read_storage.yml b/.github/workflows/read_storage.yml index 8a423d6c26..b9ff687ffc 100644 --- a/.github/workflows/read_storage.yml +++ b/.github/workflows/read_storage.yml @@ -13,13 +13,17 @@ 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: build: name: Test slither-read-storage runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup node uses: actions/setup-node@v2 with: @@ -36,7 +40,6 @@ jobs: - 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 diff --git a/.gitignore b/.gitignore index f43c11c11f..229fe33e91 100644 --- a/.gitignore +++ b/.gitignore @@ -109,8 +109,11 @@ ENV/ # Test results test_artifacts/ -# crytic export +# crytic export crytic-export/ # pipenv related Pipfile.lock + +# Auto-generated Github pages docs +docs/ diff --git a/README.md b/README.md index f005c2a1f7..bce20bfb07 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,19 @@ # Slither, the Solidity source analyzer Logo -[![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/). diff --git a/scripts/ci_test_etherscan.sh b/scripts/ci_test_etherscan.sh index 6946906919..366b0283d5 100755 --- a/scripts/ci_test_etherscan.sh +++ b/scripts/ci_test_etherscan.sh @@ -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 diff --git a/scripts/ci_test_printers.sh b/scripts/ci_test_printers.sh index e542286fe4..f6eaf0fc84 100755 --- a/scripts/ci_test_printers.sh +++ b/scripts/ci_test_printers.sh @@ -5,11 +5,20 @@ cd tests/ast-parsing/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 diff --git a/scripts/ci_test_upgradability.sh b/scripts/ci_test_upgradability.sh index b345640034..0a0d77f519 100755 --- a/scripts/ci_test_upgradability.sh +++ b/scripts/ci_test_upgradability.sh @@ -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 diff --git a/setup.py b/setup.py index a5f5006042..1dbce4c764 100644 --- a/setup.py +++ b/setup.py @@ -8,15 +8,16 @@ 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": [ @@ -24,11 +25,12 @@ "pylint==2.13.4", "pytest", "pytest-cov", + "pytest-xdist", "deepdiff", "numpy", - "solc-select>=v1.0.0b1", "openai", - ] + "pdoc", + ], }, license="AGPL-3.0", long_description=long_description, diff --git a/slither/__main__.py b/slither/__main__.py index 9d611532ea..5d0dda9e04 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -25,7 +25,13 @@ 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 ( @@ -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) @@ -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: @@ -511,7 +514,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"], @@ -754,7 +757,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) @@ -865,7 +868,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: diff --git a/slither/core/cfg/node.py b/slither/core/cfg/node.py index 530de7766a..82387d00a3 100644 --- a/slither/core/cfg/node.py +++ b/slither/core/cfg/node.py @@ -4,16 +4,19 @@ from enum import Enum from typing import Optional, List, Set, Dict, Tuple, Union, TYPE_CHECKING +from slither.all_exceptions import SlitherException from slither.core.children.child_function import ChildFunction +from slither.core.declarations import Contract, Function from slither.core.declarations.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, @@ -38,10 +41,6 @@ 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,80 +65,41 @@ 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 @@ -158,7 +118,7 @@ def __init__( node_id: int, scope: Union["Scope", "Function"], file_scope: "FileScope", - ): + ) -> None: super().__init__() self._node_type = node_type @@ -517,11 +477,11 @@ def expression(self) -> Optional[Expression]: """ 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: @@ -554,7 +514,7 @@ def contains_require_or_assert(self) -> bool: 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: @@ -564,7 +524,7 @@ def contains_if(self, include_loop=True) -> bool: 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 @@ -593,7 +553,7 @@ def is_conditional(self, include_loop=True) -> bool: 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 @@ -603,7 +563,7 @@ def add_inline_asm(self, asm: Union[str, Dict]): ################################################################################### ################################################################################### - def add_father(self, father: "Node"): + def add_father(self, father: "Node") -> None: """Add a father node Args: @@ -628,7 +588,7 @@ def fathers(self) -> List["Node"]: """ 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: @@ -636,7 +596,7 @@ def remove_father(self, father: "Node"): """ 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: @@ -644,7 +604,7 @@ def remove_son(self, son: "Node"): """ 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: @@ -652,7 +612,7 @@ def add_son(self, son: "Node"): """ 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,14 +670,14 @@ def irs_ssa(self) -> List[Operation]: def irs_ssa(self, irs): 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) 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) @@ -734,11 +694,11 @@ def all_slithir_operations(self) -> List[Operation]: 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 @@ -789,7 +749,7 @@ def dominance_frontier(self, doms: Set["Node"]): self._dominance_frontier = doms @property - def dominator_successors(self): + def dominator_successors(self) -> Set["Node"]: return self._dom_successors @property @@ -831,14 +791,14 @@ def phi_origins_state_variables( # 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: 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, @@ -862,7 +822,7 @@ def add_phi_origin_state_variable(self, variable: StateVariable, node: "Node"): ################################################################################### ################################################################################### - 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: @@ -938,7 +898,7 @@ def _find_read_write_call(self): # pylint: disable=too-many-statements 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]]: if isinstance(v, StateIRVariable): contract = v.contract non_ssa_var = contract.get_state_variable_from_name(v.name) @@ -948,7 +908,7 @@ def _convert_ssa(v: Variable): 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: @@ -1012,13 +972,13 @@ def update_read_write_using_ssa(self): ################################################################################### ################################################################################### - 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 = self._node_type.value + additional_info return txt @@ -1030,12 +990,12 @@ def __str__(self): ################################################################################### -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: diff --git a/slither/core/cfg/scope.py b/slither/core/cfg/scope.py index 06e68c3e99..d3ac4e8368 100644 --- a/slither/core/cfg/scope.py +++ b/slither/core/cfg/scope.py @@ -7,7 +7,7 @@ # 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 diff --git a/slither/core/children/child_contract.py b/slither/core/children/child_contract.py index 285623b0e1..86f9dea532 100644 --- a/slither/core/children/child_contract.py +++ b/slither/core/children/child_contract.py @@ -7,11 +7,11 @@ class ChildContract(SourceMapping): - def __init__(self): + def __init__(self) -> None: super().__init__() self._contract = None - def set_contract(self, contract: "Contract"): + def set_contract(self, contract: "Contract") -> None: self._contract = contract @property diff --git a/slither/core/children/child_event.py b/slither/core/children/child_event.py index 6df697747f..df91596e34 100644 --- a/slither/core/children/child_event.py +++ b/slither/core/children/child_event.py @@ -5,7 +5,7 @@ class ChildEvent: - def __init__(self): + def __init__(self) -> None: super().__init__() self._event = None diff --git a/slither/core/children/child_expression.py b/slither/core/children/child_expression.py index bc88ea7a43..0064658c01 100644 --- a/slither/core/children/child_expression.py +++ b/slither/core/children/child_expression.py @@ -1,17 +1,18 @@ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Union if TYPE_CHECKING: from slither.core.expressions.expression import Expression + from slither.slithir.operations import Operation class ChildExpression: - def __init__(self): + def __init__(self) -> None: super().__init__() self._expression = None - def set_expression(self, expression: "Expression"): + def set_expression(self, expression: Union["Expression", "Operation"]) -> None: self._expression = expression @property - def expression(self) -> "Expression": + def expression(self) -> Union["Expression", "Operation"]: return self._expression diff --git a/slither/core/children/child_inheritance.py b/slither/core/children/child_inheritance.py index 5efe534121..30b32f6c19 100644 --- a/slither/core/children/child_inheritance.py +++ b/slither/core/children/child_inheritance.py @@ -5,11 +5,11 @@ class ChildInheritance: - def __init__(self): + def __init__(self) -> None: super().__init__() self._contract_declarer = None - def set_contract_declarer(self, contract: "Contract"): + def set_contract_declarer(self, contract: "Contract") -> None: self._contract_declarer = contract @property diff --git a/slither/core/children/child_node.py b/slither/core/children/child_node.py index c1fffd49af..8e6e1f0b5d 100644 --- a/slither/core/children/child_node.py +++ b/slither/core/children/child_node.py @@ -7,11 +7,11 @@ class ChildNode: - def __init__(self): + def __init__(self) -> None: super().__init__() self._node = None - def set_node(self, node: "Node"): + def set_node(self, node: "Node") -> None: self._node = node @property diff --git a/slither/core/children/child_structure.py b/slither/core/children/child_structure.py index 0f4c7db824..abcb041c21 100644 --- a/slither/core/children/child_structure.py +++ b/slither/core/children/child_structure.py @@ -5,11 +5,11 @@ class ChildStructure: - def __init__(self): + def __init__(self) -> None: super().__init__() self._structure = None - def set_structure(self, structure: "Structure"): + def set_structure(self, structure: "Structure") -> None: self._structure = structure @property diff --git a/slither/core/compilation_unit.py b/slither/core/compilation_unit.py index 6f7e73b8f9..3444bd7fea 100644 --- a/slither/core/compilation_unit.py +++ b/slither/core/compilation_unit.py @@ -17,10 +17,10 @@ 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 @@ -32,7 +32,7 @@ # pylint: disable=too-many-instance-attributes,too-many-public-methods class SlitherCompilationUnit(Context): - def __init__(self, core: "SlitherCore", crytic_compilation_unit: CompilationUnit, generates_certik_ir: bool): + def __init__(self, core: "SlitherCore", crytic_compilation_unit: CompilationUnit, generates_certik_ir: bool) -> None: super().__init__() self._core = core @@ -160,21 +160,21 @@ def get_contract_from_name(self, contract_name: Union[str, Constant]) -> List[Co 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]: 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: @@ -270,7 +270,7 @@ def get_scope(self, filename_str: str) -> FileScope: ################################################################################### ################################################################################### - def compute_storage_layout(self): + def compute_storage_layout(self) -> None: for contract in self.contracts_derived: self._storage_layouts[contract.name] = {} diff --git a/slither/core/declarations/__init__.py b/slither/core/declarations/__init__.py index 0e25776be4..039debc6c4 100644 --- a/slither/core/declarations/__init__.py +++ b/slither/core/declarations/__init__.py @@ -18,4 +18,4 @@ from .function_contract import FunctionContract from .custom_error import CustomError from .function_top_level import FunctionTopLevel - +from .custom_error_contract import CustomErrorContract diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index c421b07315..e5429d8126 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -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 @@ -36,18 +36,19 @@ Event, Modifier, EnumContract, + Structure, StructureContract, FunctionContract, + FunctionTopLevel, + 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.declarations.structure import Structure + 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.declarations.function_top_level import FunctionTopLevel from slither.core.solidity_types.user_defined_type import UserDefinedType + from slither.core.cfg.node import Node + LOGGER = logging.getLogger("Contract") @@ -57,7 +58,7 @@ 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 @@ -89,6 +90,7 @@ def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope self._is_interface: bool = False self._is_abstract: bool = False self._is_library: bool = False + self._is_fully_implemented: bool = False self._signatures: Optional[List[str]] = None @@ -115,6 +117,8 @@ def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope Dict["StateVariable", Set[Union["StateVariable", "Function"]]] ] = None + self._comments: Optional[str] = None + ################################################################################### ################################################################################### # region General's properties @@ -178,6 +182,39 @@ def is_library(self) -> bool: def is_library(self, is_library: bool): 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 ################################################################################### ################################################################################### @@ -388,7 +425,7 @@ def state_variables_ordered(self) -> List["StateVariable"]: """ 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 @@ -556,7 +593,7 @@ def available_functions_as_dict(self) -> Dict[str, "FunctionContract"]: def add_function(self, func: "FunctionContract"): 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 @@ -600,7 +637,7 @@ def modifiers(self) -> List["Modifier"]: 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 @@ -710,7 +747,7 @@ def set_inheritance( 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 @@ -825,23 +862,25 @@ def get_state_variable_from_canonical_name( """ 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) @@ -1238,7 +1277,7 @@ def is_test(self) -> bool: ################################################################################### ################################################################################### - def update_read_write_using_ssa(self): + def update_read_write_using_ssa(self) -> None: for function in self.functions + self.modifiers: function.update_read_write_using_ssa() @@ -1333,7 +1372,7 @@ def is_incorrectly_constructed(self) -> bool: def is_incorrectly_constructed(self, incorrect: bool): 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: @@ -1402,7 +1441,7 @@ def add_constructor_variables(self): 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, @@ -1434,7 +1473,7 @@ def _create_node( ################################################################################### ################################################################################### - 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 @@ -1459,7 +1498,7 @@ def convert_expression_to_slithir_ssa(self): for func in self.functions + self.modifiers: func.generate_slithir_ssa(all_ssa_state_variables_instances) - def fix_phi(self): + def fix_phi(self) -> None: last_state_variables_instances = {} initial_state_variables_instances = {} for v in self._initial_state_variables: @@ -1481,20 +1520,20 @@ def fix_phi(self): ################################################################################### ################################################################################### - def __eq__(self, other): + def __eq__(self, other: SourceMapping) -> 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): + def __hash__(self) -> int: return self._id # endregion diff --git a/slither/core/declarations/custom_error.py b/slither/core/declarations/custom_error.py index a1a689fcc8..5e851c8da1 100644 --- a/slither/core/declarations/custom_error.py +++ b/slither/core/declarations/custom_error.py @@ -9,7 +9,7 @@ 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 @@ def name(self, new_name: str) -> None: 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 @@ def compilation_unit(self) -> "SlitherCompilationUnit": ################################################################################### @staticmethod - def _convert_type_for_solidity_signature(t: Optional[Union[Type, List[Type]]]): + def _convert_type_for_solidity_signature(t: Optional[Union[Type, List[Type]]]) -> str: # pylint: disable=import-outside-toplevel from slither.core.declarations import Contract @@ -92,5 +92,5 @@ def full_name(self) -> Optional[str]: ################################################################################### ################################################################################### - def __str__(self): + def __str__(self) -> str: return "revert " + self.solidity_signature diff --git a/slither/core/declarations/enum.py b/slither/core/declarations/enum.py index c53c1c38d8..66a02fd11d 100644 --- a/slither/core/declarations/enum.py +++ b/slither/core/declarations/enum.py @@ -4,7 +4,7 @@ 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 @@ def min(self) -> int: def max(self) -> int: return self._max - def __str__(self): + def __str__(self) -> str: return self.name diff --git a/slither/core/declarations/enum_top_level.py b/slither/core/declarations/enum_top_level.py index 2546b376b4..2a94c5bb1a 100644 --- a/slither/core/declarations/enum_top_level.py +++ b/slither/core/declarations/enum_top_level.py @@ -8,6 +8,8 @@ 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 diff --git a/slither/core/declarations/event.py b/slither/core/declarations/event.py index 9e445ee74b..d616679a22 100644 --- a/slither/core/declarations/event.py +++ b/slither/core/declarations/event.py @@ -9,7 +9,7 @@ class Event(ChildContract, SourceMapping): - def __init__(self): + def __init__(self) -> None: super().__init__() self._name = None self._elems: List[EventVariable] = [] @@ -59,5 +59,5 @@ def is_declared_by(self, contract: "Contract") -> bool: """ return self.contract == contract - def __str__(self): + def __str__(self) -> str: return self.name diff --git a/slither/core/declarations/function.py b/slither/core/declarations/function.py index 2fdea72109..c383fc99b0 100644 --- a/slither/core/declarations/function.py +++ b/slither/core/declarations/function.py @@ -6,7 +6,7 @@ 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.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,8 @@ 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 + from slither.core.declarations.function_contract import FunctionContract LOGGER = logging.getLogger("Function") ReacheableNode = namedtuple("ReacheableNode", ["node", "ir"]) @@ -56,7 +59,7 @@ def __init__( modifier: Union["Contract", "Function"], entry_point: "Node", nodes: List["Node"], - ): + ) -> None: self._modifier = modifier self._entry_point = entry_point self._nodes = nodes @@ -116,7 +119,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 +298,7 @@ def contains_assembly(self) -> bool: def contains_assembly(self, c: bool): self._contains_assembly = c - def can_reenter(self, callstack=None) -> bool: + def can_reenter(self, callstack: Optional[List["FunctionContract"]] = None) -> bool: """ Check if the function can re-enter Follow internal calls. @@ -370,7 +373,7 @@ def file_scope(self) -> "FileScope": ################################################################################### ################################################################################### - 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 +458,7 @@ def visibility(self) -> str: 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 +557,7 @@ def entry_point(self) -> Optional["Node"]: 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 +601,7 @@ def parameters(self) -> List["LocalVariable"]: """ return list(self._parameters) - def add_parameters(self, p: "LocalVariable"): + def add_parameters(self, p: "LocalVariable") -> None: self._parameters.append(p) @property @@ -608,7 +611,7 @@ def parameters_ssa(self) -> List["LocalIRVariable"]: """ 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 +654,7 @@ def returns(self) -> List["LocalVariable"]: """ return list(self._returns) - def add_return(self, r: "LocalVariable"): + def add_return(self, r: "LocalVariable") -> None: self._returns.append(r) @property @@ -661,7 +664,7 @@ def returns_ssa(self) -> List["LocalIRVariable"]: """ 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 +683,7 @@ def modifiers(self) -> List[Union["Contract", "Function"]]: """ 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 +717,7 @@ def explicit_base_constructor_calls_statements(self) -> List[ModifierStatements] # 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 +1060,7 @@ def all_reachable_from_functions(self) -> Set["Function"]: 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 +1071,7 @@ def add_reachable_from_node(self, n: "Node", ir: "Operation"): ################################################################################### ################################################################################### - 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 +1221,13 @@ def _explore_func_conditional( 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 +1263,9 @@ def _solidity_variable_in_internal_calls(node: "Node") -> List[SolidityVariable] 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 +1374,7 @@ def slithir_cfg_to_dot(self, filename: str): 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 +1385,7 @@ def slithir_cfg_to_dot_str(self, skip_expressions=False) -> str: 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 +1519,7 @@ def is_reentrant(self) -> bool: ################################################################################### ################################################################################### - 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 +1577,7 @@ def _analyze_read_write(self): 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 +1709,7 @@ def get_last_ssa_local_variables_instances( 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 +1718,11 @@ def _unchange_phi(ir: "Operation"): 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["StateIRVariable"]], + initial_state_variables_instances: Dict[str, "StateIRVariable"], + ) -> None: from slither.slithir.operations import InternalCall, PhiCallback from slither.slithir.variables import Constant, StateIRVariable @@ -1745,7 +1756,7 @@ def fix_phi(self, last_state_variables_instances, initial_state_variables_instan 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 +1767,7 @@ def generate_slithir_and_analyze(self): 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 +1778,7 @@ def update_read_write_using_ssa(self): ################################################################################### ################################################################################### - def __str__(self): + def __str__(self) -> str: return self.name # endregion diff --git a/slither/core/declarations/function_contract.py b/slither/core/declarations/function_contract.py index 2235bd227d..01077353b1 100644 --- a/slither/core/declarations/function_contract.py +++ b/slither/core/declarations/function_contract.py @@ -1,17 +1,20 @@ """ Function module """ -from typing import TYPE_CHECKING, List, Tuple +from typing import Dict, TYPE_CHECKING, List, Tuple from slither.core.children.child_contract import ChildContract from slither.core.children.child_inheritance import ChildInheritance 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 class FunctionContract(Function, ChildContract, ChildInheritance): @@ -71,7 +74,7 @@ def functions_shadowed(self) -> List["Function"]: 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 +90,7 @@ def get_summary( [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 +100,9 @@ def get_summary( ################################################################################### ################################################################################### - 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, diff --git a/slither/core/declarations/function_top_level.py b/slither/core/declarations/function_top_level.py index d710330695..407a8d0458 100644 --- a/slither/core/declarations/function_top_level.py +++ b/slither/core/declarations/function_top_level.py @@ -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 @@ 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 @@ def get_summary( ################################################################################### ################################################################################### - 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 ( diff --git a/slither/core/declarations/import_directive.py b/slither/core/declarations/import_directive.py index 745f8007f0..75c0406fec 100644 --- a/slither/core/declarations/import_directive.py +++ b/slither/core/declarations/import_directive.py @@ -8,7 +8,7 @@ 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 diff --git a/slither/core/declarations/pragma_directive.py b/slither/core/declarations/pragma_directive.py index 602dab6b2c..cd790d5a47 100644 --- a/slither/core/declarations/pragma_directive.py +++ b/slither/core/declarations/pragma_directive.py @@ -7,7 +7,7 @@ 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 @@ def is_abi_encoder_v2(self) -> bool: return self._directive[0] == "experimental" and self._directive[1] == "ABIEncoderV2" return False - def __str__(self): + def __str__(self) -> str: return "pragma " + "".join(self.directive) diff --git a/slither/core/declarations/solidity_import_placeholder.py b/slither/core/declarations/solidity_import_placeholder.py index 070d3fff37..8f63600867 100644 --- a/slither/core/declarations/solidity_import_placeholder.py +++ b/slither/core/declarations/solidity_import_placeholder.py @@ -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 @@ def __init__(self, import_directive: Import): 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 diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py index 939904d1bb..b4805b3f93 100644 --- a/slither/core/declarations/solidity_variables.py +++ b/slither/core/declarations/solidity_variables.py @@ -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", @@ -101,13 +99,13 @@ 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 @@ -127,18 +125,18 @@ def name(self) -> str: 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: SourceMapping) -> 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 @@ -149,13 +147,13 @@ def name(self) -> str: 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) @@ -165,7 +163,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 @@ -190,28 +188,28 @@ def return_type(self) -> List[Union[TypeInformation, ElementaryType]]: def return_type(self, r: List[Union[TypeInformation, ElementaryType]]): self._return_type = r - def __str__(self): + def __str__(self) -> str: return self._name - def __eq__(self, other): + def __eq__(self, other: "SolidityFunction") -> 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: Union["SolidityCustomRevert", SolidityFunction]) -> 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)) diff --git a/slither/core/declarations/structure.py b/slither/core/declarations/structure.py index 8f6d8c50a9..5b514e3cb2 100644 --- a/slither/core/declarations/structure.py +++ b/slither/core/declarations/structure.py @@ -51,3 +51,17 @@ def elems_ordered(self) -> List["StructureVariable"]: 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) diff --git a/slither/core/declarations/structure_top_level.py b/slither/core/declarations/structure_top_level.py index f06cd2318e..f4e2b8a9c8 100644 --- a/slither/core/declarations/structure_top_level.py +++ b/slither/core/declarations/structure_top_level.py @@ -9,6 +9,6 @@ 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 diff --git a/slither/core/declarations/using_for_top_level.py b/slither/core/declarations/using_for_top_level.py index a1b43e1c16..27d1f90e47 100644 --- a/slither/core/declarations/using_for_top_level.py +++ b/slither/core/declarations/using_for_top_level.py @@ -8,11 +8,11 @@ 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[Union[str, Type], List[Type]]: return self._using_for diff --git a/slither/core/dominators/utils.py b/slither/core/dominators/utils.py index 837fe46ea5..ca5c51282e 100644 --- a/slither/core/dominators/utils.py +++ b/slither/core/dominators/utils.py @@ -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 @@ 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' diff --git a/slither/core/expressions/assignment_operation.py b/slither/core/expressions/assignment_operation.py index b5fd3f4a3e..22aba57fb0 100644 --- a/slither/core/expressions/assignment_operation.py +++ b/slither/core/expressions/assignment_operation.py @@ -85,7 +85,7 @@ def __init__( right_expression: Expression, expression_type: AssignmentOperationType, expression_return_type: Optional["Type"], - ): + ) -> None: assert isinstance(left_expression, Expression) assert isinstance(right_expression, Expression) super().__init__() diff --git a/slither/core/expressions/call_expression.py b/slither/core/expressions/call_expression.py index 8ab6668fe8..1dbc4074a8 100644 --- a/slither/core/expressions/call_expression.py +++ b/slither/core/expressions/call_expression.py @@ -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 @@ -53,7 +53,7 @@ def arguments(self) -> List[Expression]: 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 "" diff --git a/slither/core/expressions/conditional_expression.py b/slither/core/expressions/conditional_expression.py index adcf8bb1fb..818425ba1c 100644 --- a/slither/core/expressions/conditional_expression.py +++ b/slither/core/expressions/conditional_expression.py @@ -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) diff --git a/slither/core/expressions/elementary_type_name_expression.py b/slither/core/expressions/elementary_type_name_expression.py index 0a310c86ac..9a93f0839a 100644 --- a/slither/core/expressions/elementary_type_name_expression.py +++ b/slither/core/expressions/elementary_type_name_expression.py @@ -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 @@ def type(self, new_type: Type): assert isinstance(new_type, Type) self._type = new_type - def __str__(self): + def __str__(self) -> str: return str(self._type) diff --git a/slither/core/expressions/index_access.py b/slither/core/expressions/index_access.py index 49b2a8dd9c..4f96a56d6f 100644 --- a/slither/core/expressions/index_access.py +++ b/slither/core/expressions/index_access.py @@ -1,6 +1,8 @@ -from typing import List, TYPE_CHECKING +from typing import Union, List, TYPE_CHECKING from slither.core.expressions.expression_typed import ExpressionTyped +from slither.core.expressions.identifier import Identifier +from slither.core.expressions.literal import Literal if TYPE_CHECKING: @@ -9,7 +11,12 @@ class IndexAccess(ExpressionTyped): - def __init__(self, left_expression, right_expression, index_type): + def __init__( + self, + left_expression: Union["IndexAccess", Identifier], + right_expression: Union[Literal, Identifier], + index_type: str, + ) -> None: super().__init__() self._expressions = [left_expression, right_expression] # TODO type of undexAccess is not always a Type @@ -32,5 +39,5 @@ def expression_right(self) -> "Expression": def type(self) -> "Type": return self._type - def __str__(self): + def __str__(self) -> str: return str(self.expression_left) + "[" + str(self.expression_right) + "]" diff --git a/slither/core/expressions/literal.py b/slither/core/expressions/literal.py index f7439bc69e..52ea38aaec 100644 --- a/slither/core/expressions/literal.py +++ b/slither/core/expressions/literal.py @@ -31,7 +31,7 @@ def charToSol(c): class Literal(Expression): def __init__( self, value: Union[int, str], custom_type: "Type", subdenomination: Optional[str] = None - ): + ) -> None: super().__init__() self._value = value self._type = custom_type diff --git a/slither/core/expressions/member_access.py b/slither/core/expressions/member_access.py index 73bd926412..36d6818b2a 100644 --- a/slither/core/expressions/member_access.py +++ b/slither/core/expressions/member_access.py @@ -5,7 +5,7 @@ class MemberAccess(ExpressionTyped): - def __init__(self, member_name, member_type, 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 +26,5 @@ def member_name(self) -> str: def type(self) -> Type: return self._type - def __str__(self): + def __str__(self) -> str: return str(self.expression) + "." + self.member_name diff --git a/slither/core/expressions/new_array.py b/slither/core/expressions/new_array.py index 59c5780d45..162b48f1ec 100644 --- a/slither/core/expressions/new_array.py +++ b/slither/core/expressions/new_array.py @@ -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 diff --git a/slither/core/expressions/new_contract.py b/slither/core/expressions/new_contract.py index 762a3dcfed..70f930aad8 100644 --- a/slither/core/expressions/new_contract.py +++ b/slither/core/expressions/new_contract.py @@ -2,7 +2,7 @@ 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 @@ def call_salt(self): def call_salt(self, salt): self._salt = salt - def __str__(self): + def __str__(self) -> str: return "new " + str(self._contract_name) diff --git a/slither/core/expressions/type_conversion.py b/slither/core/expressions/type_conversion.py index 97c70f45b9..b9cd6879e1 100644 --- a/slither/core/expressions/type_conversion.py +++ b/slither/core/expressions/type_conversion.py @@ -1,10 +1,27 @@ +from typing import Union, TYPE_CHECKING + from slither.core.expressions.expression_typed import ExpressionTyped from slither.core.expressions.expression import Expression from slither.core.solidity_types.type import Type +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): + 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) @@ -15,5 +32,5 @@ def __init__(self, expression, expression_type): def expression(self) -> Expression: return self._expression - def __str__(self): + def __str__(self) -> str: return str(self.type) + "(" + str(self.expression) + ")" diff --git a/slither/core/expressions/unary_operation.py b/slither/core/expressions/unary_operation.py index 596d9dbf0d..a04c575915 100644 --- a/slither/core/expressions/unary_operation.py +++ b/slither/core/expressions/unary_operation.py @@ -1,9 +1,15 @@ 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 +26,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 +49,7 @@ def get_type(operation_type, isprefix): 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 +71,7 @@ def __str__(self): 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, @@ -86,7 +92,11 @@ def is_prefix(operation_type): class UnaryOperation(ExpressionTyped): - def __init__(self, expression, expression_type): + 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 +124,7 @@ def type(self) -> UnaryOperationType: 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) diff --git a/slither/core/scope/scope.py b/slither/core/scope/scope.py index 1eb344c2be..cafeb3585d 100644 --- a/slither/core/scope/scope.py +++ b/slither/core/scope/scope.py @@ -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] = [] diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index e2fbec0aea..89f1cb9a77 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -8,7 +8,7 @@ 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 @@ -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 @@ def __init__(self): # 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] = [] @@ -452,7 +452,7 @@ def valid_result(self, r: Dict) -> bool: return True - def load_previous_results(self): + def load_previous_results(self) -> None: filename = self._previous_results_filename try: if os.path.isfile(filename): @@ -465,7 +465,7 @@ def load_previous_results(self): 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 @@ -473,7 +473,7 @@ def write_results_to_hide(self): 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): diff --git a/slither/core/solidity_types/array_type.py b/slither/core/solidity_types/array_type.py index 59a15dcc64..9a0b12c00f 100644 --- a/slither/core/solidity_types/array_type.py +++ b/slither/core/solidity_types/array_type.py @@ -1,13 +1,24 @@ -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.solidity_types.type import Type from slither.visitors.expression.constants_folding import ConstantFolding +from slither.core.expressions.literal import Literal + +if TYPE_CHECKING: + from slither.core.expressions.binary_operation import BinaryOperation + from slither.core.expressions.identifier import Identifier + from slither.core.solidity_types.elementary_type import ElementaryType + from slither.core.solidity_types.function_type import FunctionType + from slither.core.solidity_types.type_alias import TypeAliasTopLevel class ArrayType(Type): - def __init__(self, t, length): + def __init__( + self, + t: Union["TypeAliasTopLevel", "ArrayType", "FunctionType", "ElementaryType"], + length: Optional[Union["Identifier", Literal, "BinaryOperation", int]], + ) -> None: assert isinstance(t, Type) if length: if isinstance(length, int): @@ -56,15 +67,15 @@ def storage_size(self) -> Tuple[int, bool]: 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)) diff --git a/slither/core/solidity_types/elementary_type.py b/slither/core/solidity_types/elementary_type.py index fc248e9465..ec2b0ef044 100644 --- a/slither/core/solidity_types/elementary_type.py +++ b/slither/core/solidity_types/elementary_type.py @@ -216,13 +216,13 @@ def max(self) -> int: 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) -> bool: if not isinstance(other, ElementaryType): return False return self.type == other.type - def __hash__(self): + def __hash__(self) -> int: return hash(str(self)) diff --git a/slither/core/solidity_types/function_type.py b/slither/core/solidity_types/function_type.py index 3146aa0bf7..2d644148e0 100644 --- a/slither/core/solidity_types/function_type.py +++ b/slither/core/solidity_types/function_type.py @@ -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 @@ def __init__( 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 @@ def signature(self) -> str: 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 diff --git a/slither/core/solidity_types/mapping_type.py b/slither/core/solidity_types/mapping_type.py index fea5bdb7b0..a8acb4d9c4 100644 --- a/slither/core/solidity_types/mapping_type.py +++ b/slither/core/solidity_types/mapping_type.py @@ -1,10 +1,18 @@ -from typing import Tuple +from typing import Union, Tuple, TYPE_CHECKING 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,7 +35,7 @@ def storage_size(self) -> Tuple[int, bool]: 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): @@ -35,5 +43,5 @@ def __eq__(self, other): 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)) diff --git a/slither/core/solidity_types/type_alias.py b/slither/core/solidity_types/type_alias.py index 128d12597a..5b9ea0a37f 100644 --- a/slither/core/solidity_types/type_alias.py +++ b/slither/core/solidity_types/type_alias.py @@ -2,7 +2,7 @@ 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.solidity_types import Type, ElementaryType if TYPE_CHECKING: from slither.core.declarations import Contract @@ -10,13 +10,13 @@ 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 @@ def type(self) -> 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 @@ def is_dynamic(self) -> bool: class TypeAliasTopLevel(TypeAlias, TopLevel): - def __init__(self, underlying_type: Type, name: str, scope: "FileScope"): + def __init__(self, underlying_type: Type, 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"): + def __init__(self, underlying_type: Type, 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 diff --git a/slither/core/solidity_types/type_information.py b/slither/core/solidity_types/type_information.py index 0477bb7e6f..2af0b097ac 100644 --- a/slither/core/solidity_types/type_information.py +++ b/slither/core/solidity_types/type_information.py @@ -1,16 +1,17 @@ -from typing import TYPE_CHECKING, Tuple +from typing import Union, TYPE_CHECKING, Tuple 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 @@ def __init__(self, c): self._type = c @property - def type(self) -> "Contract": + def type(self) -> Union["Contract", ElementaryType, "Enum"]: return self._type @property diff --git a/slither/core/solidity_types/user_defined_type.py b/slither/core/solidity_types/user_defined_type.py index 38300cdd91..a9bbd40a21 100644 --- a/slither/core/solidity_types/user_defined_type.py +++ b/slither/core/solidity_types/user_defined_type.py @@ -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 @@ # 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 @@ def storage_size(self) -> Tuple[int, bool]: 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 @@ def __str__(self): 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)) diff --git a/slither/core/source_mapping/source_mapping.py b/slither/core/source_mapping/source_mapping.py index ee5211c7c2..e58b04c212 100644 --- a/slither/core/source_mapping/source_mapping.py +++ b/slither/core/source_mapping/source_mapping.py @@ -2,8 +2,8 @@ from abc import ABCMeta from typing import Dict, Union, List, Tuple, TYPE_CHECKING, Optional +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 @@ # 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 @@ def __init__(self) -> None: 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,27 +51,47 @@ def to_markdown(self, markdown_root: str) -> str: 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() @@ -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 diff --git a/slither/core/variables/__init__.py b/slither/core/variables/__init__.py index e69de29bb2..638f0f3a4e 100644 --- a/slither/core/variables/__init__.py +++ b/slither/core/variables/__init__.py @@ -0,0 +1,2 @@ +from .state_variable import StateVariable +from .variable import Variable diff --git a/slither/core/variables/event_variable.py b/slither/core/variables/event_variable.py index ca2f405701..f3ad60d0b7 100644 --- a/slither/core/variables/event_variable.py +++ b/slither/core/variables/event_variable.py @@ -3,7 +3,7 @@ class EventVariable(ChildEvent, Variable): - def __init__(self): + def __init__(self) -> None: super().__init__() self._indexed = False diff --git a/slither/core/variables/local_variable_init_from_tuple.py b/slither/core/variables/local_variable_init_from_tuple.py index 86a7cbbc20..8d584b3738 100644 --- a/slither/core/variables/local_variable_init_from_tuple.py +++ b/slither/core/variables/local_variable_init_from_tuple.py @@ -13,7 +13,7 @@ class LocalVariableInitFromTuple(LocalVariable): """ - def __init__(self): + def __init__(self) -> None: super().__init__() self._tuple_index: Optional[int] = None diff --git a/slither/core/variables/state_variable.py b/slither/core/variables/state_variable.py index c9a90f36b6..47b7682a47 100644 --- a/slither/core/variables/state_variable.py +++ b/slither/core/variables/state_variable.py @@ -9,7 +9,7 @@ class StateVariable(ChildContract, Variable): - def __init__(self): + def __init__(self) -> None: super().__init__() self._node_initialization: Optional["Node"] = None diff --git a/slither/core/variables/top_level_variable.py b/slither/core/variables/top_level_variable.py index 6d821092e3..e6447c1ef0 100644 --- a/slither/core/variables/top_level_variable.py +++ b/slither/core/variables/top_level_variable.py @@ -9,7 +9,7 @@ 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 diff --git a/slither/core/variables/variable.py b/slither/core/variables/variable.py index 5fda02e93a..8607a89217 100644 --- a/slither/core/variables/variable.py +++ b/slither/core/variables/variable.py @@ -12,7 +12,7 @@ # pylint: disable=too-many-instance-attributes class Variable(SourceMapping): - def __init__(self): + def __init__(self) -> None: super().__init__() self._name: Optional[str] = None self._initial_expression: Optional["Expression"] = None diff --git a/slither/detectors/abstract_detector.py b/slither/detectors/abstract_detector.py index e844bf4b93..2724d1faf1 100644 --- a/slither/detectors/abstract_detector.py +++ b/slither/detectors/abstract_detector.py @@ -5,9 +5,9 @@ from slither.core.compilation_unit import SlitherCompilationUnit from slither.core.declarations import Contract -from slither.utils.colors import green, yellow, red from slither.formatters.exceptions import FormatImpossible from slither.formatters.utils.patches import apply_patch, create_diff +from slither.utils.colors import green, yellow, red from slither.utils.comparable_enum import ComparableEnum from slither.utils.output import Output, SupportedOutput @@ -81,7 +81,7 @@ class AbstractDetector(metaclass=abc.ABCMeta): def __init__( self, compilation_unit: SlitherCompilationUnit, slither: "Slither", logger: Logger - ): + ) -> None: self.compilation_unit: SlitherCompilationUnit = compilation_unit self.contracts: List[Contract] = compilation_unit.contracts self.slither: "Slither" = slither diff --git a/slither/detectors/all_detectors.py b/slither/detectors/all_detectors.py index 1cab317adf..9722b87937 100644 --- a/slither/detectors/all_detectors.py +++ b/slither/detectors/all_detectors.py @@ -88,3 +88,4 @@ from .functions.protected_variable import ProtectedVariables from .functions.permit_domain_signature_collision import DomainSeparatorCollision from .functions.codex import Codex +from .functions.cyclomatic_complexity import CyclomaticComplexity diff --git a/slither/detectors/assembly/shift_parameter_mixup.py b/slither/detectors/assembly/shift_parameter_mixup.py index 65a35d8c3d..31dad23716 100644 --- a/slither/detectors/assembly/shift_parameter_mixup.py +++ b/slither/detectors/assembly/shift_parameter_mixup.py @@ -1,6 +1,9 @@ +from typing import List from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import Binary, BinaryType from slither.slithir.variables import Constant +from slither.core.declarations.function_contract import FunctionContract +from slither.utils.output import Output class ShiftParameterMixup(AbstractDetector): @@ -36,7 +39,7 @@ class ShiftParameterMixup(AbstractDetector): WIKI_RECOMMENDATION = "Swap the order of parameters." - def _check_function(self, f): + def _check_function(self, f: FunctionContract) -> List[Output]: results = [] for node in f.nodes: @@ -52,7 +55,7 @@ def _check_function(self, f): results.append(json) return results - def _detect(self): + def _detect(self) -> List[Output]: results = [] for c in self.contracts: for f in c.functions: diff --git a/slither/detectors/attributes/const_functions_asm.py b/slither/detectors/attributes/const_functions_asm.py index 33853c9f48..e3a9383614 100644 --- a/slither/detectors/attributes/const_functions_asm.py +++ b/slither/detectors/attributes/const_functions_asm.py @@ -2,12 +2,14 @@ Module detecting constant functions Recursively check the called functions """ +from typing import List from slither.detectors.abstract_detector import ( AbstractDetector, DetectorClassification, ALL_SOLC_VERSIONS_04, ) from slither.formatters.attributes.const_functions import custom_format +from slither.utils.output import Output class ConstantFunctionsAsm(AbstractDetector): @@ -55,7 +57,7 @@ class ConstantFunctionsAsm(AbstractDetector): VULNERABLE_SOLC_VERSIONS = ALL_SOLC_VERSIONS_04 - def _detect(self): + def _detect(self) -> List[Output]: """Detect the constant function using assembly code Recursively visit the calls diff --git a/slither/detectors/attributes/const_functions_state.py b/slither/detectors/attributes/const_functions_state.py index a351727cf0..36ea8f32d6 100644 --- a/slither/detectors/attributes/const_functions_state.py +++ b/slither/detectors/attributes/const_functions_state.py @@ -2,12 +2,14 @@ Module detecting constant functions Recursively check the called functions """ +from typing import List from slither.detectors.abstract_detector import ( AbstractDetector, DetectorClassification, ALL_SOLC_VERSIONS_04, ) from slither.formatters.attributes.const_functions import custom_format +from slither.utils.output import Output class ConstantFunctionsState(AbstractDetector): @@ -55,7 +57,7 @@ class ConstantFunctionsState(AbstractDetector): VULNERABLE_SOLC_VERSIONS = ALL_SOLC_VERSIONS_04 - def _detect(self): + def _detect(self) -> List[Output]: """Detect the constant function changing the state Recursively visit the calls diff --git a/slither/detectors/attributes/constant_pragma.py b/slither/detectors/attributes/constant_pragma.py index 0c77b69ca9..2164a78e8c 100644 --- a/slither/detectors/attributes/constant_pragma.py +++ b/slither/detectors/attributes/constant_pragma.py @@ -1,9 +1,11 @@ """ Check that the same pragma is used in all the files """ +from typing import List from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.formatters.attributes.constant_pragma import custom_format +from slither.utils.output import Output class ConstantPragma(AbstractDetector): @@ -22,7 +24,7 @@ class ConstantPragma(AbstractDetector): WIKI_DESCRIPTION = "Detect whether different Solidity versions are used." WIKI_RECOMMENDATION = "Use one Solidity version." - def _detect(self): + def _detect(self) -> List[Output]: results = [] pragma = self.compilation_unit.pragma_directives versions = [p.version for p in pragma if p.is_solidity_version] diff --git a/slither/detectors/attributes/incorrect_solc.py b/slither/detectors/attributes/incorrect_solc.py index d6838328e4..73874cffc6 100644 --- a/slither/detectors/attributes/incorrect_solc.py +++ b/slither/detectors/attributes/incorrect_solc.py @@ -3,8 +3,11 @@ """ import re +from typing import List, Optional, Tuple + from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.formatters.attributes.incorrect_solc import custom_format +from slither.utils.output import Output # group: # 0: ^ > >= < <= (optional) @@ -40,10 +43,7 @@ class IncorrectSolc(AbstractDetector): # region wiki_recommendation WIKI_RECOMMENDATION = """ Deploy with any of the following Solidity versions: -- 0.5.16 - 0.5.17 -- 0.6.11 - 0.6.12 -- 0.7.5 - 0.7.6 -- 0.8.16 +- 0.8.18 The recommendations take into account: - Risks related to recent releases @@ -59,13 +59,14 @@ class IncorrectSolc(AbstractDetector): OLD_VERSION_TXT = "allows old versions" LESS_THAN_TXT = "uses lesser than" - TOO_RECENT_VERSION_TXT = "necessitates a version too recent to be trusted. Consider deploying with 0.6.12/0.7.6/0.8.16" BUGGY_VERSION_TXT = ( "is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)" ) # Indicates the allowed versions. Must be formatted in increasing order. - ALLOWED_VERSIONS = ["0.5.16", "0.5.17", "0.6.11", "0.6.12", "0.7.5", "0.7.6", "0.8.16"] + ALLOWED_VERSIONS = ["0.8.18"] + + TOO_RECENT_VERSION_TXT = f"necessitates a version too recent to be trusted. Consider deploying with {'/'.join(ALLOWED_VERSIONS)}." # Indicates the versions that should not be used. BUGGY_VERSIONS = [ @@ -83,7 +84,7 @@ class IncorrectSolc(AbstractDetector): "^0.8.8", ] - def _check_version(self, version): + def _check_version(self, version: Tuple[str, str, str, str, str]) -> Optional[str]: op = version[0] if op and op not in [">", ">=", "^"]: return self.LESS_THAN_TXT @@ -96,7 +97,7 @@ def _check_version(self, version): return self.OLD_VERSION_TXT return None - def _check_pragma(self, version): + def _check_pragma(self, version: str) -> Optional[str]: if version in self.BUGGY_VERSIONS: return self.BUGGY_VERSION_TXT versions = PATTERN.findall(version) @@ -117,7 +118,7 @@ def _check_pragma(self, version): return self._check_version(version_left) return self.COMPLEX_PRAGMA_TXT - def _detect(self): + def _detect(self) -> List[Output]: """ Detects pragma statements that allow for outdated solc versions. :return: Returns the relevant JSON data for the findings. diff --git a/slither/detectors/attributes/locked_ether.py b/slither/detectors/attributes/locked_ether.py index e023f467b2..2fdabaea67 100644 --- a/slither/detectors/attributes/locked_ether.py +++ b/slither/detectors/attributes/locked_ether.py @@ -1,7 +1,9 @@ """ Check if ethers are locked in the contract """ +from typing import List +from slither.core.declarations.contract import Contract from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import ( HighLevelCall, @@ -12,6 +14,7 @@ LibraryCall, InternalCall, ) +from slither.utils.output import Output class LockedEther(AbstractDetector): # pylint: disable=too-many-nested-blocks @@ -41,7 +44,7 @@ class LockedEther(AbstractDetector): # pylint: disable=too-many-nested-blocks WIKI_RECOMMENDATION = "Remove the payable attribute or add a withdraw function." @staticmethod - def do_no_send_ether(contract): + def do_no_send_ether(contract: Contract) -> bool: functions = contract.all_functions_called to_explore = functions explored = [] @@ -73,7 +76,7 @@ def do_no_send_ether(contract): return True - def _detect(self): + def _detect(self) -> List[Output]: results = [] for contract in self.compilation_unit.contracts_derived: diff --git a/slither/detectors/attributes/unimplemented_interface.py b/slither/detectors/attributes/unimplemented_interface.py index c5cf5d321f..ff0889d116 100644 --- a/slither/detectors/attributes/unimplemented_interface.py +++ b/slither/detectors/attributes/unimplemented_interface.py @@ -4,8 +4,10 @@ Collect all the interfaces Check for contracts which implement all interface functions but do not explicitly derive from those interfaces. """ - +from typing import List from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.declarations.contract import Contract +from slither.utils.output import Output class MissingInheritance(AbstractDetector): @@ -42,7 +44,9 @@ class MissingInheritance(AbstractDetector): WIKI_RECOMMENDATION = "Inherit from the missing interface or contract." @staticmethod - def detect_unimplemented_interface(contract, interfaces): + def detect_unimplemented_interface( + contract: Contract, interfaces: List[Contract] + ) -> List[Contract]: """ Detects if contract intends to implement one of the interfaces but does not explicitly do so by deriving from it :param contract: The contract to check @@ -50,7 +54,7 @@ def detect_unimplemented_interface(contract, interfaces): :return: Interfaces likely intended to implement by the contract """ - intended_interfaces = [] + intended_interfaces: List[Contract] = [] sigs_contract = {f.full_name for f in contract.functions_entry_points} if not sigs_contract: @@ -111,7 +115,7 @@ def detect_unimplemented_interface(contract, interfaces): return intended_interfaces - def _detect(self): + def _detect(self) -> List[Output]: """Detect unimplemented interfaces Returns: list: {'contract'} diff --git a/slither/detectors/compiler_bugs/array_by_reference.py b/slither/detectors/compiler_bugs/array_by_reference.py index 6acc78d17c..83ed69b9b6 100644 --- a/slither/detectors/compiler_bugs/array_by_reference.py +++ b/slither/detectors/compiler_bugs/array_by_reference.py @@ -1,13 +1,17 @@ """ Detects the passing of arrays located in memory to functions which expect to modify arrays via storage reference. """ - +from typing import List, Set, Tuple, Union from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.core.solidity_types.array_type import ArrayType from slither.core.variables.state_variable import StateVariable from slither.core.variables.local_variable import LocalVariable from slither.slithir.operations.high_level_call import HighLevelCall from slither.slithir.operations.internal_call import InternalCall +from slither.core.cfg.node import Node +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract +from slither.utils.output import Output class ArrayByReference(AbstractDetector): @@ -55,7 +59,7 @@ class ArrayByReference(AbstractDetector): WIKI_RECOMMENDATION = "Ensure the correct usage of `memory` and `storage` in the function parameters. Make all the locations explicit." @staticmethod - def get_funcs_modifying_array_params(contracts): + def get_funcs_modifying_array_params(contracts: List[Contract]) -> Set[FunctionContract]: """ Obtains a set of functions which take arrays not located in storage as parameters, and writes to them. :param contracts: The collection of contracts to check functions in. @@ -83,7 +87,14 @@ def get_funcs_modifying_array_params(contracts): return results @staticmethod - def detect_calls_passing_ref_to_function(contracts, array_modifying_funcs): + def detect_calls_passing_ref_to_function( + contracts: List[Contract], array_modifying_funcs: Set[FunctionContract] + ) -> List[ + Union[ + Tuple[Node, StateVariable, FunctionContract], + Tuple[Node, LocalVariable, FunctionContract], + ] + ]: """ Obtains all calls passing storage arrays by value to a function which cannot write to them successfully. :param contracts: The collection of contracts to check for problematic calls in. @@ -134,7 +145,7 @@ def detect_calls_passing_ref_to_function(contracts, array_modifying_funcs): results.append((node, arg, ir.function)) return results - def _detect(self): + def _detect(self) -> List[Output]: """ Detects passing of arrays located in memory to functions which expect to modify arrays via storage reference. :return: The JSON results of the detector, which contains the calling_node, affected_argument_variable and diff --git a/slither/detectors/compiler_bugs/enum_conversion.py b/slither/detectors/compiler_bugs/enum_conversion.py index 477188fe08..671b8d6995 100644 --- a/slither/detectors/compiler_bugs/enum_conversion.py +++ b/slither/detectors/compiler_bugs/enum_conversion.py @@ -1,7 +1,11 @@ """ Module detecting dangerous conversion to enum """ +from typing import List, Tuple +from slither.core.cfg.node import Node +from slither.core.declarations import Contract +from slither.core.source_mapping.source_mapping import SourceMapping from slither.detectors.abstract_detector import ( AbstractDetector, DetectorClassification, @@ -9,9 +13,10 @@ ) from slither.slithir.operations import TypeConversion from slither.core.declarations.enum import Enum +from slither.utils.output import Output -def _detect_dangerous_enum_conversions(contract): +def _detect_dangerous_enum_conversions(contract: Contract) -> List[Tuple[Node, SourceMapping]]: """Detect dangerous conversion to enum by checking IR Args: contract (Contract) @@ -61,7 +66,7 @@ class EnumConversion(AbstractDetector): VULNERABLE_SOLC_VERSIONS = make_solc_versions(4, 0, 4) - def _detect(self): + def _detect(self) -> List[Output]: """Detect dangerous conversion to enum""" results = [] diff --git a/slither/detectors/compiler_bugs/multiple_constructor_schemes.py b/slither/detectors/compiler_bugs/multiple_constructor_schemes.py index 5845cea1c4..3486cc41b1 100644 --- a/slither/detectors/compiler_bugs/multiple_constructor_schemes.py +++ b/slither/detectors/compiler_bugs/multiple_constructor_schemes.py @@ -1,4 +1,7 @@ +from typing import List + from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output class MultipleConstructorSchemes(AbstractDetector): @@ -43,7 +46,7 @@ class MultipleConstructorSchemes(AbstractDetector): WIKI_RECOMMENDATION = "Only declare one constructor, preferably using the new scheme `constructor(...)` instead of `function (...)`." - def _detect(self): + def _detect(self) -> List[Output]: """ Detect multiple constructor schemes in the same contract :return: Returns a list of contract JSON result, where each result contains all constructor definitions. diff --git a/slither/detectors/compiler_bugs/public_mapping_nested.py b/slither/detectors/compiler_bugs/public_mapping_nested.py index 8e6b6f4a81..0ae8c3d509 100644 --- a/slither/detectors/compiler_bugs/public_mapping_nested.py +++ b/slither/detectors/compiler_bugs/public_mapping_nested.py @@ -1,7 +1,7 @@ """ Module detecting public mappings with nested variables (returns incorrect values prior to 0.5.x) """ - +from typing import Any, List, Union from slither.detectors.abstract_detector import ( AbstractDetector, DetectorClassification, @@ -10,9 +10,12 @@ from slither.core.solidity_types.mapping_type import MappingType from slither.core.solidity_types.user_defined_type import UserDefinedType from slither.core.declarations.structure import Structure +from slither.core.declarations.contract import Contract +from slither.core.variables.state_variable import StateVariable +from slither.utils.output import Output -def detect_public_nested_mappings(contract): +def detect_public_nested_mappings(contract: Contract) -> List[Union[StateVariable, Any]]: """ Detect any state variables that are initialized from an immediate function call (prior to constructor run). :param contract: The contract to detect state variable definitions for. @@ -68,7 +71,7 @@ class PublicMappingNested(AbstractDetector): VULNERABLE_SOLC_VERSIONS = ALL_SOLC_VERSIONS_04 - def _detect(self): + def _detect(self) -> List[Output]: """ Detect public mappings with nested variables (returns incorrect values prior to 0.5.x) diff --git a/slither/detectors/compiler_bugs/reused_base_constructor.py b/slither/detectors/compiler_bugs/reused_base_constructor.py index 9d0b91448b..73cfac12e9 100644 --- a/slither/detectors/compiler_bugs/reused_base_constructor.py +++ b/slither/detectors/compiler_bugs/reused_base_constructor.py @@ -1,18 +1,24 @@ """ Module detecting re-used base constructors in inheritance hierarchy. """ - +from typing import Any, Dict, List, Tuple, Union from slither.detectors.abstract_detector import ( AbstractDetector, DetectorClassification, ALL_SOLC_VERSIONS_04, ) +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract +from slither.utils.output import Output # Helper: adds explicitly called constructors with arguments to the results lookup. def _add_constructors_with_args( - base_constructors, called_by_constructor, current_contract, results -): + base_constructors: List[Union[Any, FunctionContract]], + called_by_constructor: bool, + current_contract: Contract, + results: Dict[FunctionContract, List[Tuple[Contract, bool]]], +) -> None: for explicit_base_constructor in base_constructors: if len(explicit_base_constructor.parameters) > 0: if explicit_base_constructor not in results: @@ -77,7 +83,9 @@ class ReusedBaseConstructor(AbstractDetector): VULNERABLE_SOLC_VERSIONS = ALL_SOLC_VERSIONS_04 - def _detect_explicitly_called_base_constructors(self, contract): + def _detect_explicitly_called_base_constructors( + self, contract: Contract + ) -> Dict[FunctionContract, List[Tuple[Contract, bool]]]: """ Detects explicitly calls to base constructors with arguments in the inheritance hierarchy. :param contract: The contract to detect explicit calls to a base constructor with arguments to. @@ -124,7 +132,7 @@ def _detect_explicitly_called_base_constructors(self, contract): return results - def _detect(self): + def _detect(self) -> List[Output]: """ Detect reused base constructors. :return: Returns a list of JSON results. diff --git a/slither/detectors/compiler_bugs/storage_ABIEncoderV2_array.py b/slither/detectors/compiler_bugs/storage_ABIEncoderV2_array.py index 59d52760e7..aee6361c64 100644 --- a/slither/detectors/compiler_bugs/storage_ABIEncoderV2_array.py +++ b/slither/detectors/compiler_bugs/storage_ABIEncoderV2_array.py @@ -1,7 +1,7 @@ """ Module detecting ABIEncoderV2 array bug """ - +from typing import List, Set, Tuple from slither.detectors.abstract_detector import ( AbstractDetector, DetectorClassification, @@ -16,6 +16,10 @@ from slither.slithir.operations import EventCall from slither.slithir.operations import HighLevelCall from slither.utils.utils import unroll +from slither.core.cfg.node import Node +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract +from slither.utils.output import Output class ABIEncoderV2Array(AbstractDetector): @@ -55,7 +59,9 @@ class ABIEncoderV2Array(AbstractDetector): VULNERABLE_SOLC_VERSIONS = make_solc_versions(4, 7, 25) + make_solc_versions(5, 0, 9) @staticmethod - def _detect_storage_abiencoderv2_arrays(contract): + def _detect_storage_abiencoderv2_arrays( + contract: Contract, + ) -> Set[Tuple[FunctionContract, Node]]: """ Detects and returns all nodes with storage-allocated abiencoderv2 arrays of arrays/structs in abi.encode, events or external calls :param contract: Contract to detect within @@ -98,7 +104,7 @@ def _detect_storage_abiencoderv2_arrays(contract): # Return the resulting set of tuples return results - def _detect(self): + def _detect(self) -> List[Output]: """ Detect ABIEncoderV2 array bug """ diff --git a/slither/detectors/compiler_bugs/storage_signed_integer_array.py b/slither/detectors/compiler_bugs/storage_signed_integer_array.py index 419c71c872..736f667892 100644 --- a/slither/detectors/compiler_bugs/storage_signed_integer_array.py +++ b/slither/detectors/compiler_bugs/storage_signed_integer_array.py @@ -1,6 +1,7 @@ """ Module detecting storage signed integer array bug """ +from typing import List from slither.detectors.abstract_detector import ( AbstractDetector, @@ -14,6 +15,7 @@ from slither.core.variables.state_variable import StateVariable from slither.slithir.operations.assignment import Assignment from slither.slithir.operations.init_array import InitArray +from slither.utils.output import Output class StorageSignedIntegerArray(AbstractDetector): @@ -108,7 +110,7 @@ def detect_storage_signed_integer_arrays(self, contract): # Return the resulting set of tuples return results - def _detect(self): + def _detect(self) -> List[Output]: """ Detect storage signed integer array init/assignment """ diff --git a/slither/detectors/compiler_bugs/uninitialized_function_ptr_in_constructor.py b/slither/detectors/compiler_bugs/uninitialized_function_ptr_in_constructor.py index a4d3cb8f2b..6685948b35 100644 --- a/slither/detectors/compiler_bugs/uninitialized_function_ptr_in_constructor.py +++ b/slither/detectors/compiler_bugs/uninitialized_function_ptr_in_constructor.py @@ -1,7 +1,7 @@ """ Module detecting uninitialized function pointer calls in constructors """ - +from typing import Any, List, Union from slither.detectors.abstract_detector import ( AbstractDetector, DetectorClassification, @@ -10,9 +10,14 @@ from slither.slithir.operations import InternalDynamicCall, OperationWithLValue from slither.slithir.variables import ReferenceVariable from slither.slithir.variables.variable import SlithIRVariable +from slither.core.cfg.node import Node +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract +from slither.slithir.variables.state_variable import StateIRVariable +from slither.utils.output import Output -def _get_variables_entrance(function): +def _get_variables_entrance(function: FunctionContract) -> List[Union[Any, StateIRVariable]]: """ Return the first SSA variables of the function Catpure the phi operation at the entry point @@ -25,7 +30,7 @@ def _get_variables_entrance(function): return ret -def _is_vulnerable(node, variables_entrance): +def _is_vulnerable(node: Node, variables_entrance: List[Union[Any, StateIRVariable]]) -> bool: """ Vulnerable if an IR ssa: - It is an internal dynamic call @@ -84,7 +89,9 @@ class UninitializedFunctionPtrsConstructor(AbstractDetector): VULNERABLE_SOLC_VERSIONS = make_solc_versions(4, 5, 25) + make_solc_versions(5, 0, 8) @staticmethod - def _detect_uninitialized_function_ptr_in_constructor(contract): + def _detect_uninitialized_function_ptr_in_constructor( + contract: Contract, + ) -> List[Union[Any, Node]]: """ Detect uninitialized function pointer calls in constructors :param contract: The contract of interest for detection @@ -99,7 +106,7 @@ def _detect_uninitialized_function_ptr_in_constructor(contract): ] return results - def _detect(self): + def _detect(self) -> List[Output]: """ Detect uninitialized function pointer calls in constructors of contracts Returns: diff --git a/slither/detectors/erc/erc20/arbitrary_send_erc20.py b/slither/detectors/erc/erc20/arbitrary_send_erc20.py index 7aeaa1139e..17b1fba30f 100644 --- a/slither/detectors/erc/erc20/arbitrary_send_erc20.py +++ b/slither/detectors/erc/erc20/arbitrary_send_erc20.py @@ -1,16 +1,17 @@ from typing import List + +from slither.analyses.data_dependency.data_dependency import is_dependent from slither.core.cfg.node import Node +from slither.core.compilation_unit import SlitherCompilationUnit +from slither.core.declarations import Contract, Function, SolidityVariableComposed from slither.core.declarations.solidity_variables import SolidityVariable from slither.slithir.operations import HighLevelCall, LibraryCall -from slither.core.declarations import Contract, Function, SolidityVariableComposed -from slither.analyses.data_dependency.data_dependency import is_dependent -from slither.core.compilation_unit import SlitherCompilationUnit class ArbitrarySendErc20: """Detects instances where ERC20 can be sent from an arbitrary from address.""" - def __init__(self, compilation_unit: SlitherCompilationUnit): + def __init__(self, compilation_unit: SlitherCompilationUnit) -> None: self._compilation_unit = compilation_unit self._no_permit_results: List[Node] = [] self._permit_results: List[Node] = [] @@ -27,7 +28,7 @@ def no_permit_results(self) -> List[Node]: def permit_results(self) -> List[Node]: return self._permit_results - def _detect_arbitrary_from(self, contract: Contract): + def _detect_arbitrary_from(self, contract: Contract) -> None: for f in contract.functions: all_high_level_calls = [ f_called[1].solidity_signature @@ -48,7 +49,7 @@ def _detect_arbitrary_from(self, contract: Contract): ArbitrarySendErc20._arbitrary_from(f.nodes, self._no_permit_results) @staticmethod - def _arbitrary_from(nodes: List[Node], results: List[Node]): + def _arbitrary_from(nodes: List[Node], results: List[Node]) -> None: """Finds instances of (safe)transferFrom that do not use msg.sender or address(this) as from parameter.""" for node in nodes: for ir in node.irs: @@ -89,7 +90,7 @@ def _arbitrary_from(nodes: List[Node], results: List[Node]): ): results.append(ir.node) - def detect(self): + def detect(self) -> None: """Detect transfers that use arbitrary `from` parameter.""" for c in self.compilation_unit.contracts_derived: self._detect_arbitrary_from(c) diff --git a/slither/detectors/erc/erc20/incorrect_erc20_interface.py b/slither/detectors/erc/erc20/incorrect_erc20_interface.py index aa9f3a9160..4da6ab5ae0 100644 --- a/slither/detectors/erc/erc20/incorrect_erc20_interface.py +++ b/slither/detectors/erc/erc20/incorrect_erc20_interface.py @@ -2,7 +2,12 @@ Detect incorrect erc20 interface. Some contracts do not return a bool on transfer/transferFrom/approve, which may lead to preventing the contract to be used with contracts compiled with recent solc (>0.4.22) """ +from typing import List, Tuple + +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output class IncorrectERC20InterfaceDetection(AbstractDetector): @@ -36,7 +41,7 @@ class IncorrectERC20InterfaceDetection(AbstractDetector): ) @staticmethod - def incorrect_erc20_interface(signature): + def incorrect_erc20_interface(signature: Tuple[str, List[str], List[str]]) -> bool: (name, parameters, returnVars) = signature if name == "transfer" and parameters == ["address", "uint256"] and returnVars != ["bool"]: @@ -68,7 +73,7 @@ def incorrect_erc20_interface(signature): return False @staticmethod - def detect_incorrect_erc20_interface(contract): + def detect_incorrect_erc20_interface(contract: Contract) -> List[FunctionContract]: """Detect incorrect ERC20 interface Returns: @@ -93,7 +98,7 @@ def detect_incorrect_erc20_interface(contract): return functions - def _detect(self): + def _detect(self) -> List[Output]: """Detect incorrect erc20 interface Returns: diff --git a/slither/detectors/erc/incorrect_erc721_interface.py b/slither/detectors/erc/incorrect_erc721_interface.py index 2bdc78cd75..8327e8b2ee 100644 --- a/slither/detectors/erc/incorrect_erc721_interface.py +++ b/slither/detectors/erc/incorrect_erc721_interface.py @@ -1,7 +1,11 @@ """ Detect incorrect erc721 interface. """ +from typing import Any, List, Tuple, Union from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract +from slither.utils.output import Output class IncorrectERC721InterfaceDetection(AbstractDetector): @@ -37,7 +41,9 @@ class IncorrectERC721InterfaceDetection(AbstractDetector): ) @staticmethod - def incorrect_erc721_interface(signature): + def incorrect_erc721_interface( + signature: Union[Tuple[str, List[str], List[str]], Tuple[str, List[str], List[Any]]] + ) -> bool: (name, parameters, returnVars) = signature # ERC721 @@ -83,7 +89,7 @@ def incorrect_erc721_interface(signature): return False @staticmethod - def detect_incorrect_erc721_interface(contract): + def detect_incorrect_erc721_interface(contract: Contract) -> List[Union[FunctionContract, Any]]: """Detect incorrect ERC721 interface Returns: @@ -102,7 +108,7 @@ def detect_incorrect_erc721_interface(contract): ] return functions - def _detect(self): + def _detect(self) -> List[Output]: """Detect incorrect erc721 interface Returns: diff --git a/slither/detectors/erc/unindexed_event_parameters.py b/slither/detectors/erc/unindexed_event_parameters.py index 3962e23583..6e91b0fb36 100644 --- a/slither/detectors/erc/unindexed_event_parameters.py +++ b/slither/detectors/erc/unindexed_event_parameters.py @@ -1,7 +1,12 @@ """ Detect mistakenly un-indexed ERC20 event parameters """ +from typing import Any, List, Tuple, Union from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.declarations.contract import Contract +from slither.core.declarations.event import Event +from slither.core.variables.event_variable import EventVariable +from slither.utils.output import Output class UnindexedERC20EventParameters(AbstractDetector): @@ -39,7 +44,9 @@ class UnindexedERC20EventParameters(AbstractDetector): STANDARD_JSON = False @staticmethod - def detect_erc20_unindexed_event_params(contract): + def detect_erc20_unindexed_event_params( + contract: Contract, + ) -> List[Union[Tuple[Event, EventVariable], Any]]: """ Detect un-indexed ERC20 event parameters in a given contract. :param contract: The contract to check ERC20 events for un-indexed parameters in. @@ -68,7 +75,7 @@ def detect_erc20_unindexed_event_params(contract): # Return the results. return results - def _detect(self): + def _detect(self) -> List[Output]: """ Detect un-indexed ERC20 event parameters in all contracts. """ diff --git a/slither/detectors/examples/backdoor.py b/slither/detectors/examples/backdoor.py index 1e73fa8140..0e8e9ad81f 100644 --- a/slither/detectors/examples/backdoor.py +++ b/slither/detectors/examples/backdoor.py @@ -1,4 +1,7 @@ +from typing import List + from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output class Backdoor(AbstractDetector): @@ -17,7 +20,7 @@ class Backdoor(AbstractDetector): WIKI_EXPLOIT_SCENARIO = ".." WIKI_RECOMMENDATION = ".." - def _detect(self): + def _detect(self) -> List[Output]: results = [] for contract in self.compilation_unit.contracts_derived: diff --git a/slither/detectors/functions/arbitrary_send_eth.py b/slither/detectors/functions/arbitrary_send_eth.py index e1752bbdb0..390b1f2abf 100644 --- a/slither/detectors/functions/arbitrary_send_eth.py +++ b/slither/detectors/functions/arbitrary_send_eth.py @@ -9,11 +9,12 @@ TODO: dont report if the value is tainted by msg.value """ -from typing import List +from typing import Any, Tuple, Union, List +from slither.analyses.data_dependency.data_dependency import is_tainted, is_dependent from slither.core.cfg.node import Node from slither.core.declarations import Function, Contract -from slither.analyses.data_dependency.data_dependency import is_tainted, is_dependent +from slither.core.declarations.function_contract import FunctionContract from slither.core.declarations.solidity_variables import ( SolidityFunction, SolidityVariableComposed, @@ -28,12 +29,11 @@ Transfer, ) - # pylint: disable=too-many-nested-blocks,too-many-branches from slither.utils.output import Output -def arbitrary_send(func: Function): +def arbitrary_send(func: Function) -> Union[bool, List[Node]]: if func.is_protected(): return [] @@ -74,7 +74,9 @@ def arbitrary_send(func: Function): return ret -def detect_arbitrary_send(contract: Contract): +def detect_arbitrary_send( + contract: Contract, +) -> List[Union[Tuple[FunctionContract, List[Node]], Any]]: """ Detect arbitrary send Args: diff --git a/slither/detectors/functions/codex.py b/slither/detectors/functions/codex.py index fb00f64c04..e58658a3a3 100644 --- a/slither/detectors/functions/codex.py +++ b/slither/detectors/functions/codex.py @@ -52,6 +52,10 @@ def _run_codex(self, logging_file: str, prompt: str) -> str: answer = "" res = {} + + if self.slither.codex_organization: + openai_module.organization = self.slither.codex_organization + try: res = openai_module.Completion.create( prompt=prompt, @@ -114,11 +118,7 @@ def _detect(self) -> List[Output]: ): continue prompt = f"Analyze this Solidity contract and find the vulnerabilities. If you find any vulnerabilities, begin the response with {VULN_FOUND}\n" - src_mapping = contract.source_mapping - content = contract.compilation_unit.core.source_code[src_mapping.filename.absolute] - start = src_mapping.start - end = src_mapping.start + src_mapping.length - prompt += content[start:end] + prompt += contract.source_mapping.content answer = self._run_codex(logging_file, prompt) diff --git a/slither/detectors/functions/cyclomatic_complexity.py b/slither/detectors/functions/cyclomatic_complexity.py new file mode 100644 index 0000000000..53212fd4f9 --- /dev/null +++ b/slither/detectors/functions/cyclomatic_complexity.py @@ -0,0 +1,50 @@ +from typing import List, Tuple + +from slither.core.declarations import Function +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.code_complexity import compute_cyclomatic_complexity +from slither.utils.output import Output + + +def _check_for_high_cc(high_cc_functions: List[Tuple[Function, int]], f: Function) -> None: + cc = compute_cyclomatic_complexity(f) + if cc > 11: + high_cc_functions.append((f, cc)) + + +class CyclomaticComplexity(AbstractDetector): + """ + Detects functions with high (> 11) cyclomatic complexity. + """ + + ARGUMENT = "cyclomatic-complexity" + HELP = "Detects functions with high (> 11) cyclomatic complexity" + IMPACT = DetectorClassification.INFORMATIONAL + CONFIDENCE = DetectorClassification.HIGH + + WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity" + + WIKI_TITLE = "Cyclomatic complexity" + WIKI_DESCRIPTION = "Detects functions with high (> 11) cyclomatic complexity." + WIKI_EXPLOIT_SCENARIO = "" + WIKI_RECOMMENDATION = ( + "Reduce cyclomatic complexity by splitting the function into several smaller subroutines." + ) + + def _detect(self) -> List[Output]: + results = [] + high_cc_functions: List[Tuple[Function, int]] = [] + + f: Function + for c in self.compilation_unit.contracts: + for f in c.functions_declared: + _check_for_high_cc(high_cc_functions, f) + + for f in self.compilation_unit.functions_top_level: + _check_for_high_cc(high_cc_functions, f) + + for f, cc in high_cc_functions: + info = [f, f" has a high cyclomatic complexity ({cc}).\n"] + res = self.generate_result(info) + results.append(res) + return results diff --git a/slither/detectors/functions/dead_code.py b/slither/detectors/functions/dead_code.py index 5d632b9f91..1a25c57761 100644 --- a/slither/detectors/functions/dead_code.py +++ b/slither/detectors/functions/dead_code.py @@ -5,6 +5,7 @@ from slither.core.declarations import Function, FunctionContract, Contract from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output class DeadCode(AbstractDetector): @@ -34,7 +35,7 @@ class DeadCode(AbstractDetector): WIKI_RECOMMENDATION = "Remove unused functions." - def _detect(self): + def _detect(self) -> List[Output]: results = [] diff --git a/slither/detectors/functions/modifier.py b/slither/detectors/functions/modifier.py index 23c6fc0fcc..271d8e6cb0 100644 --- a/slither/detectors/functions/modifier.py +++ b/slither/detectors/functions/modifier.py @@ -5,18 +5,19 @@ are in the outermost scope, they do not guarantee a revert, so a default value can still be returned. """ - +from typing import List from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.core.cfg.node import NodeType +from slither.core.cfg.node import Node, NodeType +from slither.utils.output import Output -def is_revert(node): +def is_revert(node: Node) -> bool: return node.type == NodeType.THROW or any( c.name in ["revert()", "revert(string"] for c in node.internal_calls ) -def _get_false_son(node): +def _get_false_son(node: Node) -> Node: """Select the son node corresponding to a false branch Following this node stays on the outer scope of the function """ @@ -60,7 +61,7 @@ class ModifierDefaultDetection(AbstractDetector): WIKI_RECOMMENDATION = "All the paths in a modifier must execute `_` or revert." - def _detect(self): + def _detect(self) -> List[Output]: results = [] for c in self.contracts: for mod in c.modifiers: diff --git a/slither/detectors/functions/permit_domain_signature_collision.py b/slither/detectors/functions/permit_domain_signature_collision.py index 7142d7cf19..de64ec52eb 100644 --- a/slither/detectors/functions/permit_domain_signature_collision.py +++ b/slither/detectors/functions/permit_domain_signature_collision.py @@ -8,6 +8,7 @@ from slither.core.variables.state_variable import StateVariable from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.utils.function import get_function_id +from slither.utils.output import Output class DomainSeparatorCollision(AbstractDetector): @@ -39,7 +40,7 @@ class DomainSeparatorCollision(AbstractDetector): WIKI_RECOMMENDATION = "Remove or rename the function that collides with DOMAIN_SEPARATOR()." - def _detect(self): + def _detect(self) -> List[Output]: domain_sig = get_function_id("DOMAIN_SEPARATOR()") for contract in self.compilation_unit.contracts_derived: if contract.is_erc20(): diff --git a/slither/detectors/functions/protected_variable.py b/slither/detectors/functions/protected_variable.py index cbd640e18e..68ed098c79 100644 --- a/slither/detectors/functions/protected_variable.py +++ b/slither/detectors/functions/protected_variable.py @@ -5,8 +5,8 @@ """ from typing import List -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.core.declarations import Function, Contract +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.utils.output import Output @@ -71,7 +71,7 @@ def _analyze_function(self, function: Function, contract: Contract) -> List[Outp results.append(res) return results - def _detect(self): + def _detect(self) -> List[Output]: """Detect the suicidal functions""" results = [] for contract in self.compilation_unit.contracts_derived: diff --git a/slither/detectors/functions/suicidal.py b/slither/detectors/functions/suicidal.py index 906b139026..7741da57da 100644 --- a/slither/detectors/functions/suicidal.py +++ b/slither/detectors/functions/suicidal.py @@ -3,8 +3,12 @@ A suicidal contract is an unprotected function that calls selfdestruct """ +from typing import List +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output class Suicidal(AbstractDetector): @@ -37,7 +41,7 @@ class Suicidal(AbstractDetector): WIKI_RECOMMENDATION = "Protect access to all sensitive functions." @staticmethod - def detect_suicidal_func(func): + def detect_suicidal_func(func: FunctionContract) -> bool: """Detect if the function is suicidal Detect the public functions calling suicide/selfdestruct without protection @@ -60,14 +64,14 @@ def detect_suicidal_func(func): return True - def detect_suicidal(self, contract): + def detect_suicidal(self, contract: Contract) -> List[FunctionContract]: ret = [] for f in contract.functions_declared: if self.detect_suicidal_func(f): ret.append(f) return ret - def _detect(self): + def _detect(self) -> List[Output]: """Detect the suicidal functions""" results = [] for c in self.contracts: diff --git a/slither/detectors/functions/unimplemented.py b/slither/detectors/functions/unimplemented.py index 88c71ac054..11a1fad80d 100644 --- a/slither/detectors/functions/unimplemented.py +++ b/slither/detectors/functions/unimplemented.py @@ -7,8 +7,12 @@ Consider public state variables as implemented functions Do not consider fallback function or constructor """ - +from typing import List, Set from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract +from slither.utils.output import Output + # Since 0.5.1, Solidity allows creating state variable matching a function signature. older_solc_versions = ["0.5.0"] + ["0.4." + str(x) for x in range(0, 27)] @@ -55,10 +59,10 @@ class UnimplementedFunctionDetection(AbstractDetector): WIKI_RECOMMENDATION = "Implement all unimplemented functions in any contract you intend to use directly (not simply inherit from)." @staticmethod - def _match_state_variable(contract, f): + def _match_state_variable(contract: Contract, f: FunctionContract) -> bool: return any(s.full_name == f.full_name for s in contract.state_variables) - def _detect_unimplemented_function(self, contract): + def _detect_unimplemented_function(self, contract: Contract) -> Set[FunctionContract]: """ Detects any function definitions which are not implemented in the given contract. :param contract: The contract to search unimplemented functions for. @@ -87,7 +91,7 @@ def _detect_unimplemented_function(self, contract): unimplemented.add(f) return unimplemented - def _detect(self): + def _detect(self) -> List[Output]: """Detect unimplemented functions Recursively visit the calls diff --git a/slither/detectors/naming_convention/naming_convention.py b/slither/detectors/naming_convention/naming_convention.py index 5e81f003c4..96d3964fa5 100644 --- a/slither/detectors/naming_convention/naming_convention.py +++ b/slither/detectors/naming_convention/naming_convention.py @@ -1,6 +1,8 @@ import re +from typing import List from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.formatters.naming_convention.naming_convention import custom_format +from slither.utils.output import Output class NamingConvention(AbstractDetector): @@ -36,28 +38,29 @@ class NamingConvention(AbstractDetector): STANDARD_JSON = False @staticmethod - def is_cap_words(name): + def is_cap_words(name: str) -> bool: return re.search("^[A-Z]([A-Za-z0-9]+)?_?$", name) is not None @staticmethod - def is_mixed_case(name): + def is_mixed_case(name: str) -> bool: return re.search("^[a-z]([A-Za-z0-9]+)?_?$", name) is not None @staticmethod - def is_mixed_case_with_underscore(name): + def is_mixed_case_with_underscore(name: str) -> bool: # Allow _ at the beginning to represent private variable # or unused parameters return re.search("^[_]?[a-z]([A-Za-z0-9]+)?_?$", name) is not None @staticmethod - def is_upper_case_with_underscores(name): + def is_upper_case_with_underscores(name: str) -> bool: return re.search("^[A-Z0-9_]+_?$", name) is not None @staticmethod - def should_avoid_name(name): + def should_avoid_name(name: str) -> bool: return re.search("^[lOI]$", name) is not None - def _detect(self): # pylint: disable=too-many-branches,too-many-statements + # pylint: disable=too-many-branches,too-many-statements + def _detect(self) -> List[Output]: results = [] for contract in self.contracts: @@ -139,7 +142,8 @@ def _detect(self): # pylint: disable=too-many-branches,too-many-statements # For ERC20 compatibility if var.name in ["symbol", "name", "decimals"]: continue - + if var.visibility == "public": + continue if not self.is_upper_case_with_underscores(var.name): info = [ "Constant ", diff --git a/slither/detectors/operations/block_timestamp.py b/slither/detectors/operations/block_timestamp.py index 01941257d1..b80c8c392b 100644 --- a/slither/detectors/operations/block_timestamp.py +++ b/slither/detectors/operations/block_timestamp.py @@ -13,6 +13,7 @@ ) from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import Binary, BinaryType +from slither.utils.output import Output def _timestamp(func: Function) -> List[Node]: @@ -69,7 +70,7 @@ class Timestamp(AbstractDetector): WIKI_EXPLOIT_SCENARIO = """"Bob's contract relies on `block.timestamp` for its randomness. Eve is a miner and manipulates `block.timestamp` to exploit Bob's contract.""" WIKI_RECOMMENDATION = "Avoid relying on `block.timestamp`." - def _detect(self): + def _detect(self) -> List[Output]: """""" results = [] diff --git a/slither/detectors/operations/low_level_calls.py b/slither/detectors/operations/low_level_calls.py index 7e0f45e349..1ea91c37a9 100644 --- a/slither/detectors/operations/low_level_calls.py +++ b/slither/detectors/operations/low_level_calls.py @@ -1,9 +1,13 @@ """ Module detecting usage of low level calls """ - +from typing import List, Tuple from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import LowLevelCall +from slither.core.cfg.node import Node +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract +from slither.utils.output import Output class LowLevelCalls(AbstractDetector): @@ -23,7 +27,7 @@ class LowLevelCalls(AbstractDetector): WIKI_RECOMMENDATION = "Avoid low-level calls. Check the call success. If the call is meant for a contract, check for code existence." @staticmethod - def _contains_low_level_calls(node): + def _contains_low_level_calls(node: Node) -> bool: """ Check if the node contains Low Level Calls Returns: @@ -31,7 +35,9 @@ def _contains_low_level_calls(node): """ return any(isinstance(ir, LowLevelCall) for ir in node.irs) - def detect_low_level_calls(self, contract): + def detect_low_level_calls( + self, contract: Contract + ) -> List[Tuple[FunctionContract, List[Node]]]: ret = [] for f in [f for f in contract.functions if contract == f.contract_declarer]: nodes = f.nodes @@ -40,7 +46,7 @@ def detect_low_level_calls(self, contract): ret.append((f, assembly_nodes)) return ret - def _detect(self): + def _detect(self) -> List[Output]: """Detect the functions that use low level calls""" results = [] for c in self.contracts: diff --git a/slither/detectors/operations/missing_events_access_control.py b/slither/detectors/operations/missing_events_access_control.py index 7437b32abe..20c2297596 100644 --- a/slither/detectors/operations/missing_events_access_control.py +++ b/slither/detectors/operations/missing_events_access_control.py @@ -2,11 +2,18 @@ Module detecting missing events for critical contract parameters set by owners and used in access control """ +from typing import List, Tuple -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.analyses.data_dependency.data_dependency import is_tainted -from slither.slithir.operations.event_call import EventCall +from slither.core.cfg.node import Node +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract +from slither.core.declarations.modifier import Modifier from slither.core.solidity_types.elementary_type import ElementaryType +from slither.core.variables.state_variable import StateVariable +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.slithir.operations.event_call import EventCall +from slither.utils.output import Output class MissingEventsAccessControl(AbstractDetector): @@ -45,7 +52,9 @@ class MissingEventsAccessControl(AbstractDetector): WIKI_RECOMMENDATION = "Emit an event for critical parameter changes." @staticmethod - def _detect_missing_events(contract): + def _detect_missing_events( + contract: Contract, + ) -> List[Tuple[FunctionContract, List[Tuple[Node, StateVariable, Modifier]]]]: """ Detects if critical contract parameters set by owners and used in access control are missing events :param contract: The contract to check @@ -80,7 +89,7 @@ def _detect_missing_events(contract): results.append((function, nodes)) return results - def _detect(self): + def _detect(self) -> List[Output]: """Detect missing events for critical contract parameters set by owners and used in access control Returns: list: {'(function, node)'} diff --git a/slither/detectors/operations/missing_events_arithmetic.py b/slither/detectors/operations/missing_events_arithmetic.py index 340f471a5d..6e1d5fbb50 100644 --- a/slither/detectors/operations/missing_events_arithmetic.py +++ b/slither/detectors/operations/missing_events_arithmetic.py @@ -2,11 +2,17 @@ Module detecting missing events for critical contract parameters set by owners and used in arithmetic """ +from typing import List, Tuple -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.analyses.data_dependency.data_dependency import is_tainted -from slither.slithir.operations.event_call import EventCall +from slither.core.cfg.node import Node +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract from slither.core.solidity_types.elementary_type import ElementaryType, Int, Uint +from slither.core.variables.state_variable import StateVariable +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.slithir.operations.event_call import EventCall +from slither.utils.output import Output class MissingEventsArithmetic(AbstractDetector): @@ -49,7 +55,9 @@ class MissingEventsArithmetic(AbstractDetector): WIKI_RECOMMENDATION = "Emit an event for critical parameter changes." @staticmethod - def _detect_unprotected_use(contract, sv): + def _detect_unprotected_use( + contract: Contract, sv: StateVariable + ) -> List[Tuple[Node, FunctionContract]]: unprotected_functions = [ function for function in contract.functions_declared if not function.is_protected() ] @@ -60,7 +68,9 @@ def _detect_unprotected_use(contract, sv): if sv in node.state_variables_read ] - def _detect_missing_events(self, contract): + def _detect_missing_events( + self, contract: Contract + ) -> List[Tuple[FunctionContract, List[Tuple[Node, List[Tuple[Node, FunctionContract]]]]]]: """ Detects if critical contract parameters set by owners and used in arithmetic are missing events :param contract: The contract to check @@ -101,7 +111,7 @@ def _detect_missing_events(self, contract): results.append((function, nodes)) return results - def _detect(self): + def _detect(self) -> List[Output]: """Detect missing events for critical contract parameters set by owners and used in arithmetic Returns: list: {'(function, node)'} diff --git a/slither/detectors/operations/missing_zero_address_validation.py b/slither/detectors/operations/missing_zero_address_validation.py index cb6bf7cdbd..a6c8de9ff9 100644 --- a/slither/detectors/operations/missing_zero_address_validation.py +++ b/slither/detectors/operations/missing_zero_address_validation.py @@ -3,12 +3,19 @@ """ from collections import defaultdict +from typing import DefaultDict, List, Tuple, Union -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.analyses.data_dependency.data_dependency import is_tainted +from slither.core.cfg.node import Node +from slither.core.declarations.contract import Contract +from slither.core.declarations.function import ModifierStatements +from slither.core.declarations.function_contract import FunctionContract from slither.core.solidity_types.elementary_type import ElementaryType -from slither.slithir.operations import Send, Transfer, LowLevelCall +from slither.core.variables.local_variable import LocalVariable +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import Call +from slither.slithir.operations import Send, Transfer, LowLevelCall +from slither.utils.output import Output class MissingZeroAddressValidation(AbstractDetector): @@ -46,7 +53,9 @@ class MissingZeroAddressValidation(AbstractDetector): WIKI_RECOMMENDATION = "Check that the address is not zero." - def _zero_address_validation_in_modifier(self, var, modifier_exprs): + def _zero_address_validation_in_modifier( + self, var: LocalVariable, modifier_exprs: List[ModifierStatements] + ) -> bool: for mod in modifier_exprs: for node in mod.nodes: # Skip validation if the modifier's parameters contains more than one variable @@ -62,7 +71,9 @@ def _zero_address_validation_in_modifier(self, var, modifier_exprs): return True return False - def _zero_address_validation(self, var, node, explored): + def _zero_address_validation( + self, var: LocalVariable, node: Node, explored: List[Node] + ) -> bool: """ Detects (recursively) if var is (zero address) checked in the function node """ @@ -83,7 +94,9 @@ def _zero_address_validation(self, var, node, explored): return True return False - def _detect_missing_zero_address_validation(self, contract): + def _detect_missing_zero_address_validation( + self, contract: Contract + ) -> List[Union[Tuple[FunctionContract, DefaultDict[LocalVariable, List[Node]]]]]: """ Detects if addresses are zero address validated before use. :param contract: The contract to check @@ -130,7 +143,7 @@ def _detect_missing_zero_address_validation(self, contract): results.append((function, var_nodes)) return results - def _detect(self): + def _detect(self) -> List[Output]: """Detect if addresses are zero address validated before use. Returns: list: {'(function, node)'} diff --git a/slither/detectors/operations/unchecked_low_level_return_values.py b/slither/detectors/operations/unchecked_low_level_return_values.py index 5064ebca63..0537ebbf21 100644 --- a/slither/detectors/operations/unchecked_low_level_return_values.py +++ b/slither/detectors/operations/unchecked_low_level_return_values.py @@ -4,6 +4,7 @@ from slither.detectors.abstract_detector import DetectorClassification from slither.detectors.operations.unused_return_values import UnusedReturnValues from slither.slithir.operations import LowLevelCall +from slither.slithir.operations.operation import Operation class UncheckedLowLevel(UnusedReturnValues): @@ -37,5 +38,5 @@ class UncheckedLowLevel(UnusedReturnValues): WIKI_RECOMMENDATION = "Ensure that the return value of a low-level call is checked or logged." - def _is_instance(self, ir): # pylint: disable=no-self-use + def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use return isinstance(ir, LowLevelCall) diff --git a/slither/detectors/operations/unchecked_send_return_value.py b/slither/detectors/operations/unchecked_send_return_value.py index 0c3ff0d30e..e9b2dc3225 100644 --- a/slither/detectors/operations/unchecked_send_return_value.py +++ b/slither/detectors/operations/unchecked_send_return_value.py @@ -5,6 +5,7 @@ from slither.detectors.abstract_detector import DetectorClassification from slither.detectors.operations.unused_return_values import UnusedReturnValues from slither.slithir.operations import Send +from slither.slithir.operations.operation import Operation class UncheckedSend(UnusedReturnValues): @@ -38,5 +39,5 @@ class UncheckedSend(UnusedReturnValues): WIKI_RECOMMENDATION = "Ensure that the return value of `send` is checked or logged." - def _is_instance(self, ir): # pylint: disable=no-self-use + def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use return isinstance(ir, Send) diff --git a/slither/detectors/operations/unchecked_transfer.py b/slither/detectors/operations/unchecked_transfer.py index df5e8464cd..224a7cda06 100644 --- a/slither/detectors/operations/unchecked_transfer.py +++ b/slither/detectors/operations/unchecked_transfer.py @@ -6,6 +6,7 @@ from slither.detectors.abstract_detector import DetectorClassification from slither.detectors.operations.unused_return_values import UnusedReturnValues from slither.slithir.operations import HighLevelCall +from slither.slithir.operations.operation import Operation class UncheckedTransfer(UnusedReturnValues): @@ -45,7 +46,7 @@ class UncheckedTransfer(UnusedReturnValues): "Use `SafeERC20`, or ensure that the transfer/transferFrom return value is checked." ) - def _is_instance(self, ir): # pylint: disable=no-self-use + def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use return ( isinstance(ir, HighLevelCall) and isinstance(ir.function, Function) diff --git a/slither/detectors/operations/unused_return_values.py b/slither/detectors/operations/unused_return_values.py index ff3e7139db..7edde20fc5 100644 --- a/slither/detectors/operations/unused_return_values.py +++ b/slither/detectors/operations/unused_return_values.py @@ -1,11 +1,16 @@ """ Module detecting unused return values from external calls """ +from typing import List +from slither.core.cfg.node import Node +from slither.core.declarations import Function +from slither.core.declarations.function_contract import FunctionContract from slither.core.variables.state_variable import StateVariable from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import HighLevelCall -from slither.core.declarations import Function +from slither.slithir.operations.operation import Operation +from slither.utils.output import Output class UnusedReturnValues(AbstractDetector): @@ -40,7 +45,7 @@ class UnusedReturnValues(AbstractDetector): WIKI_RECOMMENDATION = "Ensure that all the return values of the function calls are used." - def _is_instance(self, ir): # pylint: disable=no-self-use + def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use return isinstance(ir, HighLevelCall) and ( ( isinstance(ir.function, Function) @@ -50,7 +55,9 @@ def _is_instance(self, ir): # pylint: disable=no-self-use or not isinstance(ir.function, Function) ) - def detect_unused_return_values(self, f): # pylint: disable=no-self-use + def detect_unused_return_values( + self, f: FunctionContract + ) -> List[Node]: # pylint: disable=no-self-use """ Return the nodes where the return value of a call is unused Args: @@ -73,7 +80,7 @@ def detect_unused_return_values(self, f): # pylint: disable=no-self-use return [nodes_origin[value].node for value in values_returned] - def _detect(self): + def _detect(self) -> List[Output]: """Detect high level calls which return a value that are never used""" results = [] for c in self.compilation_unit.contracts: diff --git a/slither/detectors/operations/void_constructor.py b/slither/detectors/operations/void_constructor.py index ca010b24db..fb44ea98c9 100644 --- a/slither/detectors/operations/void_constructor.py +++ b/slither/detectors/operations/void_constructor.py @@ -1,5 +1,8 @@ +from typing import List + from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import Nop +from slither.utils.output import Output class VoidConstructor(AbstractDetector): @@ -26,7 +29,7 @@ class VoidConstructor(AbstractDetector): When reading `B`'s constructor definition, we might assume that `A()` initiates the contract, but no code is executed.""" # endregion wiki_exploit_scenario - def _detect(self): + def _detect(self) -> List[Output]: """""" results = [] for c in self.contracts: diff --git a/slither/detectors/reentrancy/reentrancy_benign.py b/slither/detectors/reentrancy/reentrancy_benign.py index c3eeb5357e..25fe0ff038 100644 --- a/slither/detectors/reentrancy/reentrancy_benign.py +++ b/slither/detectors/reentrancy/reentrancy_benign.py @@ -5,10 +5,11 @@ Iterate over all the nodes of the graph until reaching a fixpoint """ from collections import namedtuple, defaultdict -from typing import List +from typing import DefaultDict, Set, List from slither.detectors.abstract_detector import DetectorClassification -from .reentrancy import Reentrancy, to_hashable +from slither.detectors.reentrancy.reentrancy import Reentrancy, to_hashable +from slither.utils.output import Output FindingKey = namedtuple("FindingKey", ["function", "calls", "send_eth"]) FindingValue = namedtuple("FindingValue", ["variable", "node", "nodes"]) @@ -50,7 +51,7 @@ class ReentrancyBenign(Reentrancy): STANDARD_JSON = False - def find_reentrancies(self): + def find_reentrancies(self) -> DefaultDict[FindingKey, Set[FindingValue]]: result = defaultdict(set) for contract in self.contracts: for f in contract.functions_and_modifiers_declared: @@ -87,7 +88,7 @@ def find_reentrancies(self): result[finding_key] |= not_read_then_written return result - def _detect(self): # pylint: disable=too-many-branches + def _detect(self) -> List[Output]: # pylint: disable=too-many-branches """""" super()._detect() diff --git a/slither/detectors/reentrancy/reentrancy_eth.py b/slither/detectors/reentrancy/reentrancy_eth.py index 73622cf545..ccb6688372 100644 --- a/slither/detectors/reentrancy/reentrancy_eth.py +++ b/slither/detectors/reentrancy/reentrancy_eth.py @@ -38,7 +38,7 @@ class ReentrancyEth(Reentrancy): ```solidity function withdrawBalance(){ // send userBalance[msg.sender] Ether to msg.sender - // if mgs.sender is a contract, it will call its fallback function + // if msg.sender is a contract, it will call its fallback function if( ! (msg.sender.call.value(userBalance[msg.sender])() ) ){ throw; } diff --git a/slither/detectors/reentrancy/reentrancy_events.py b/slither/detectors/reentrancy/reentrancy_events.py index 78a6058be5..2d29442f72 100644 --- a/slither/detectors/reentrancy/reentrancy_events.py +++ b/slither/detectors/reentrancy/reentrancy_events.py @@ -5,9 +5,11 @@ Iterate over all the nodes of the graph until reaching a fixpoint """ from collections import namedtuple, defaultdict +from typing import DefaultDict, List, Set from slither.detectors.abstract_detector import DetectorClassification -from .reentrancy import Reentrancy, to_hashable +from slither.detectors.reentrancy.reentrancy import Reentrancy, to_hashable +from slither.utils.output import Output FindingKey = namedtuple("FindingKey", ["function", "calls", "send_eth"]) FindingValue = namedtuple("FindingValue", ["variable", "node", "nodes"]) @@ -48,7 +50,7 @@ class ReentrancyEvent(Reentrancy): STANDARD_JSON = False - def find_reentrancies(self): + def find_reentrancies(self) -> DefaultDict[FindingKey, Set[FindingValue]]: result = defaultdict(set) for contract in self.contracts: for f in contract.functions_and_modifiers_declared: @@ -80,7 +82,7 @@ def find_reentrancies(self): result[finding_key] |= finding_vars return result - def _detect(self): # pylint: disable=too-many-branches + def _detect(self) -> List[Output]: # pylint: disable=too-many-branches """""" super()._detect() diff --git a/slither/detectors/reentrancy/reentrancy_no_gas.py b/slither/detectors/reentrancy/reentrancy_no_gas.py index 29d3b881aa..c559d76df8 100644 --- a/slither/detectors/reentrancy/reentrancy_no_gas.py +++ b/slither/detectors/reentrancy/reentrancy_no_gas.py @@ -5,11 +5,16 @@ Iterate over all the nodes of the graph until reaching a fixpoint """ from collections import namedtuple, defaultdict +from typing import DefaultDict, List, Union, Set from slither.core.variables.variable import Variable from slither.detectors.abstract_detector import DetectorClassification +from slither.detectors.reentrancy.reentrancy import Reentrancy, to_hashable from slither.slithir.operations import Send, Transfer, EventCall -from .reentrancy import Reentrancy, to_hashable +from slither.slithir.operations.high_level_call import HighLevelCall +from slither.slithir.operations.member import Member +from slither.slithir.operations.return_operation import Return +from slither.utils.output import Output FindingKey = namedtuple("FindingKey", ["function", "calls", "send_eth"]) FindingValue = namedtuple("FindingValue", ["variable", "node", "nodes"]) @@ -50,7 +55,7 @@ class ReentrancyNoGas(Reentrancy): WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)." @staticmethod - def can_callback(ir): + def can_callback(ir: Union[Member, Return, HighLevelCall]) -> bool: """ Same as Reentrancy, but also consider Send and Transfer @@ -59,8 +64,8 @@ def can_callback(ir): STANDARD_JSON = False - def find_reentrancies(self): - result = defaultdict(set) + def find_reentrancies(self) -> DefaultDict[FindingKey, Set[FindingValue]]: + result: DefaultDict[FindingKey, Set[FindingValue]] = defaultdict(set) for contract in self.contracts: for f in contract.functions_and_modifiers_declared: for node in f.nodes: @@ -97,7 +102,7 @@ def find_reentrancies(self): result[finding_key] |= finding_vars return result - def _detect(self): # pylint: disable=too-many-branches,too-many-locals + def _detect(self) -> List[Output]: # pylint: disable=too-many-branches,too-many-locals """""" super()._detect() diff --git a/slither/detectors/reentrancy/token.py b/slither/detectors/reentrancy/token.py index 9f9ba97f44..c960bffa72 100644 --- a/slither/detectors/reentrancy/token.py +++ b/slither/detectors/reentrancy/token.py @@ -6,6 +6,7 @@ from slither.core.declarations import Function, Contract, SolidityVariableComposed from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import LowLevelCall, HighLevelCall +from slither.utils.output import Output def _detect_token_reentrant(contract: Contract) -> Dict[Function, List[Node]]: @@ -82,7 +83,7 @@ class TokenReentrancy(AbstractDetector): If you do, ensure your users are aware of the potential issues.""" # endregion wiki_recommendation - def _detect(self): + def _detect(self) -> List[Output]: results = [] for contract in self.compilation_unit.contracts_derived: vulns = _detect_token_reentrant(contract) diff --git a/slither/detectors/shadowing/builtin_symbols.py b/slither/detectors/shadowing/builtin_symbols.py index 5001a108f1..b0a44c8e2d 100644 --- a/slither/detectors/shadowing/builtin_symbols.py +++ b/slither/detectors/shadowing/builtin_symbols.py @@ -1,8 +1,16 @@ """ Module detecting reserved keyword shadowing """ - +from typing import List, Tuple, Union, Optional + +from slither.core.declarations import Function, Event +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract +from slither.core.declarations.modifier import Modifier +from slither.core.variables import Variable +from slither.core.variables.local_variable import LocalVariable from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output class BuiltinSymbolShadowing(AbstractDetector): @@ -114,7 +122,7 @@ class BuiltinSymbolShadowing(AbstractDetector): "unchecked", ] - def is_builtin_symbol(self, word): + def is_builtin_symbol(self, word: Optional[str]) -> bool: """Detects if a given word is a built-in symbol. Returns: @@ -122,7 +130,9 @@ def is_builtin_symbol(self, word): return word in self.BUILTIN_SYMBOLS or word in self.RESERVED_KEYWORDS - def detect_builtin_shadowing_locals(self, function_or_modifier): + def detect_builtin_shadowing_locals( + self, function_or_modifier: Union[Modifier, FunctionContract] + ) -> List[Tuple[str, LocalVariable]]: """Detects if local variables in a given function/modifier are named after built-in symbols. Any such items are returned in a list. @@ -135,14 +145,16 @@ def detect_builtin_shadowing_locals(self, function_or_modifier): results.append((self.SHADOWING_LOCAL_VARIABLE, local)) return results - def detect_builtin_shadowing_definitions(self, contract): + def detect_builtin_shadowing_definitions( + self, contract: Contract + ) -> List[Tuple[str, Union[Function, Variable, Event]]]: """Detects if functions, access modifiers, events, state variables, or local variables are named after built-in symbols. Any such definitions are returned in a list. Returns: list of tuple: (type, definition, [local variable parent])""" - result = [] + result: List[Tuple[str, Union[Function, Variable, Event]]] = [] # Loop through all functions, modifiers, variables (state and local) to detect any built-in symbol keywords. for function in contract.functions_declared: @@ -164,7 +176,7 @@ def detect_builtin_shadowing_definitions(self, contract): return result - def _detect(self): + def _detect(self) -> List[Output]: """Detect shadowing of built-in symbols Recursively visit the calls diff --git a/slither/detectors/shadowing/local.py b/slither/detectors/shadowing/local.py index 617c24be41..07abe52489 100644 --- a/slither/detectors/shadowing/local.py +++ b/slither/detectors/shadowing/local.py @@ -1,8 +1,16 @@ """ Module detecting local variable shadowing """ - +from typing import List, Tuple, Union + +from slither.core.declarations.contract import Contract +from slither.core.declarations.event import Event +from slither.core.declarations.function_contract import FunctionContract +from slither.core.declarations.modifier import Modifier +from slither.core.variables.local_variable import LocalVariable +from slither.core.variables.state_variable import StateVariable from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output class LocalShadowing(AbstractDetector): @@ -49,14 +57,32 @@ class LocalShadowing(AbstractDetector): OVERSHADOWED_MODIFIER = "modifier" OVERSHADOWED_STATE_VARIABLE = "state variable" OVERSHADOWED_EVENT = "event" - - def detect_shadowing_definitions(self, contract): # pylint: disable=too-many-branches + OVERSHADOWED_RETURN_VARIABLE = "return variable" + + # pylint: disable=too-many-branches + def detect_shadowing_definitions( + self, contract: Contract + ) -> List[ + Union[ + Tuple[LocalVariable, List[Tuple[str, StateVariable]]], + Tuple[LocalVariable, List[Tuple[str, FunctionContract]]], + Tuple[LocalVariable, List[Tuple[str, Modifier]]], + Tuple[LocalVariable, List[Tuple[str, Event]]], + ] + ]: """Detects if functions, access modifiers, events, state variables, and local variables are named after reserved keywords. Any such definitions are returned in a list. Returns: list of tuple: (type, contract name, definition)""" - result = [] + result: List[ + Union[ + Tuple[LocalVariable, List[Tuple[str, StateVariable]]], + Tuple[LocalVariable, List[Tuple[str, FunctionContract]]], + Tuple[LocalVariable, List[Tuple[str, Modifier]]], + Tuple[LocalVariable, List[Tuple[str, Event]]], + ] + ] = [] # Loop through all functions + modifiers in this contract. for function in contract.functions + contract.modifiers: @@ -86,6 +112,15 @@ def detect_shadowing_definitions(self, contract): # pylint: disable=too-many-br overshadowed.append( (self.OVERSHADOWED_STATE_VARIABLE, scope_state_variable) ) + # Check named return variables + for named_return in function.returns: + # Shadowed local delcarations in the same function will have "_scope_" in their name. + # See `FunctionSolc._add_local_variable` + if ( + "_scope_" in variable.name + and variable.name.split("_scope_")[0] == named_return.name + ): + overshadowed.append((self.OVERSHADOWED_RETURN_VARIABLE, named_return)) # If we have found any overshadowed objects, we'll want to add it to our result list. if overshadowed: @@ -93,7 +128,7 @@ def detect_shadowing_definitions(self, contract): # pylint: disable=too-many-br return result - def _detect(self): + def _detect(self) -> List[Output]: """Detect shadowing local variables Recursively visit the calls diff --git a/slither/detectors/shadowing/state.py b/slither/detectors/shadowing/state.py index 766c2437d7..801c370a59 100644 --- a/slither/detectors/shadowing/state.py +++ b/slither/detectors/shadowing/state.py @@ -2,12 +2,16 @@ Module detecting shadowing of state variables """ -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from typing import List + from slither.core.declarations import Contract -from .common import is_upgradable_gap_variable +from slither.core.variables.state_variable import StateVariable +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.detectors.shadowing.common import is_upgradable_gap_variable +from slither.utils.output import Output -def detect_shadowing(contract: Contract): +def detect_shadowing(contract: Contract) -> List[List[StateVariable]]: ret = [] variables_fathers = [] for father in contract.inheritance: @@ -70,7 +74,7 @@ class StateShadowing(AbstractDetector): WIKI_RECOMMENDATION = "Remove the state variable shadowing." - def _detect(self): + def _detect(self) -> List[Output]: """Detect shadowing Recursively visit the calls diff --git a/slither/detectors/slither/name_reused.py b/slither/detectors/slither/name_reused.py index 2cd10ed316..f6f2820fa2 100644 --- a/slither/detectors/slither/name_reused.py +++ b/slither/detectors/slither/name_reused.py @@ -1,10 +1,12 @@ from collections import defaultdict +from typing import Any, List from slither.core.compilation_unit import SlitherCompilationUnit from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output -def _find_missing_inheritance(compilation_unit: SlitherCompilationUnit): +def _find_missing_inheritance(compilation_unit: SlitherCompilationUnit) -> List[Any]: """ Filter contracts with missing inheritance to return only the "most base" contracts in the inheritance tree. @@ -50,7 +52,8 @@ class NameReused(AbstractDetector): WIKI_RECOMMENDATION = "Rename the contract." - def _detect(self): # pylint: disable=too-many-locals,too-many-branches + # pylint: disable=too-many-locals,too-many-branches + def _detect(self) -> List[Output]: results = [] compilation_unit = self.compilation_unit diff --git a/slither/detectors/source/rtlo.py b/slither/detectors/source/rtlo.py index ed73fdd4d3..f89eb70eb2 100644 --- a/slither/detectors/source/rtlo.py +++ b/slither/detectors/source/rtlo.py @@ -1,5 +1,9 @@ import re +from typing import List + from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output + # pylint: disable=bidirectional-unicode class RightToLeftOverride(AbstractDetector): @@ -52,7 +56,7 @@ class RightToLeftOverride(AbstractDetector): RTLO_CHARACTER_ENCODED = "\u202e".encode("utf-8") STANDARD_JSON = False - def _detect(self): + def _detect(self) -> List[Output]: results = [] pattern = re.compile(".*\u202e.*".encode("utf-8")) diff --git a/slither/detectors/statements/array_length_assignment.py b/slither/detectors/statements/array_length_assignment.py index 7f875fa9e5..51302a2c94 100644 --- a/slither/detectors/statements/array_length_assignment.py +++ b/slither/detectors/statements/array_length_assignment.py @@ -1,21 +1,23 @@ """ Module detecting assignment of array length """ - +from typing import List, Set from slither.detectors.abstract_detector import ( AbstractDetector, DetectorClassification, ALL_SOLC_VERSIONS_04, ALL_SOLC_VERSIONS_05, ) -from slither.core.cfg.node import NodeType +from slither.core.cfg.node import Node, NodeType from slither.slithir.operations import Assignment, Length from slither.slithir.variables.reference import ReferenceVariable from slither.slithir.operations.binary import Binary from slither.analyses.data_dependency.data_dependency import is_tainted +from slither.core.declarations.contract import Contract +from slither.utils.output import Output -def detect_array_length_assignment(contract): +def detect_array_length_assignment(contract: Contract) -> Set[Node]: """ Detects and returns all nodes which assign array length. :param contract: Contract to detect assignment within. @@ -110,7 +112,7 @@ class ArrayLengthAssignment(AbstractDetector): VULNERABLE_SOLC_VERSIONS = ALL_SOLC_VERSIONS_04 + ALL_SOLC_VERSIONS_05 - def _detect(self): + def _detect(self) -> List[Output]: """ Detect array length assignments """ diff --git a/slither/detectors/statements/assembly.py b/slither/detectors/statements/assembly.py index 3a554e380c..2c0c49f091 100644 --- a/slither/detectors/statements/assembly.py +++ b/slither/detectors/statements/assembly.py @@ -1,9 +1,13 @@ """ Module detecting usage of inline assembly """ +from typing import List, Tuple +from slither.core.cfg.node import Node, NodeType +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.core.cfg.node import NodeType +from slither.utils.output import Output class Assembly(AbstractDetector): @@ -23,7 +27,7 @@ class Assembly(AbstractDetector): WIKI_RECOMMENDATION = "Do not use `evm` assembly." @staticmethod - def _contains_inline_assembly_use(node): + def _contains_inline_assembly_use(node: Node) -> bool: """ Check if the node contains ASSEMBLY type Returns: @@ -31,7 +35,7 @@ def _contains_inline_assembly_use(node): """ return node.type == NodeType.ASSEMBLY - def detect_assembly(self, contract): + def detect_assembly(self, contract: Contract) -> List[Tuple[FunctionContract, List[Node]]]: ret = [] for f in contract.functions: if f.contract_declarer != contract: @@ -42,7 +46,7 @@ def detect_assembly(self, contract): ret.append((f, assembly_nodes)) return ret - def _detect(self): + def _detect(self) -> List[Output]: """Detect the functions that use inline assembly""" results = [] for c in self.contracts: diff --git a/slither/detectors/statements/assert_state_change.py b/slither/detectors/statements/assert_state_change.py index 126f4b64da..c82919de68 100644 --- a/slither/detectors/statements/assert_state_change.py +++ b/slither/detectors/statements/assert_state_change.py @@ -1,11 +1,19 @@ """ Module detecting state changes in assert calls """ +from typing import List, Tuple + +from slither.core.cfg.node import Node +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations.internal_call import InternalCall +from slither.utils.output import Output -def detect_assert_state_change(contract): +def detect_assert_state_change( + contract: Contract, +) -> List[Tuple[FunctionContract, Node]]: """ Detects and returns all nodes with assert calls that change contract state from within the invariant :param contract: Contract to detect @@ -69,7 +77,7 @@ class AssertStateChange(AbstractDetector): WIKI_RECOMMENDATION = """Use `require` for invariants modifying the state.""" - def _detect(self): + def _detect(self) -> List[Output]: """ Detect assert calls that change state from within the invariant """ diff --git a/slither/detectors/statements/boolean_constant_equality.py b/slither/detectors/statements/boolean_constant_equality.py index eddea6236e..5b91f364f8 100644 --- a/slither/detectors/statements/boolean_constant_equality.py +++ b/slither/detectors/statements/boolean_constant_equality.py @@ -1,13 +1,18 @@ """ Module detecting misuse of Boolean constants """ +from typing import List, Set, Tuple +from slither.core.cfg.node import Node +from slither.core.declarations import Function +from slither.core.declarations.contract import Contract from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import ( Binary, BinaryType, ) from slither.slithir.variables import Constant +from slither.utils.output import Output class BooleanEquality(AbstractDetector): @@ -44,10 +49,12 @@ class BooleanEquality(AbstractDetector): WIKI_RECOMMENDATION = """Remove the equality to the boolean constant.""" @staticmethod - def _detect_boolean_equality(contract): + def _detect_boolean_equality( + contract: Contract, + ) -> List[Tuple[Function, Set[Node]]]: # Create our result set. - results = [] + results: List[Tuple[Function, Set[Node]]] = [] # Loop for each function and modifier. # pylint: disable=too-many-nested-blocks @@ -68,7 +75,7 @@ def _detect_boolean_equality(contract): # Return the resulting set of nodes with improper uses of Boolean constants return results - def _detect(self): + def _detect(self) -> List[Output]: """ Detect Boolean constant misuses """ diff --git a/slither/detectors/statements/boolean_constant_misuse.py b/slither/detectors/statements/boolean_constant_misuse.py index 4e7a0d69df..96dd2012f0 100644 --- a/slither/detectors/statements/boolean_constant_misuse.py +++ b/slither/detectors/statements/boolean_constant_misuse.py @@ -1,7 +1,11 @@ """ Module detecting misuse of Boolean constants """ -from slither.core.cfg.node import NodeType +from typing import List, Set, Tuple + +from slither.core.cfg.node import Node, NodeType +from slither.core.declarations import Function +from slither.core.declarations.contract import Contract from slither.core.solidity_types import ElementaryType from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import ( @@ -14,6 +18,7 @@ Condition, ) from slither.slithir.variables import Constant +from slither.utils.output import Output class BooleanConstantMisuse(AbstractDetector): @@ -59,7 +64,9 @@ class BooleanConstantMisuse(AbstractDetector): WIKI_RECOMMENDATION = """Verify and simplify the condition.""" @staticmethod - def _detect_boolean_constant_misuses(contract): # pylint: disable=too-many-branches + def _detect_boolean_constant_misuses( + contract: Contract, + ) -> List[Tuple[Function, Set[Node]]]: # pylint: disable=too-many-branches """ Detects and returns all nodes which misuse a Boolean constant. :param contract: Contract to detect assignment within. @@ -67,7 +74,7 @@ def _detect_boolean_constant_misuses(contract): # pylint: disable=too-many-bran """ # Create our result set. - results = [] + results: List[Tuple[Function, Set[Node]]] = [] # Loop for each function and modifier. for function in contract.functions_declared: @@ -104,7 +111,7 @@ def _detect_boolean_constant_misuses(contract): # pylint: disable=too-many-bran # Return the resulting set of nodes with improper uses of Boolean constants return results - def _detect(self): + def _detect(self) -> List[Output]: """ Detect Boolean constant misuses """ diff --git a/slither/detectors/statements/controlled_delegatecall.py b/slither/detectors/statements/controlled_delegatecall.py index eeac55925b..08280940d1 100644 --- a/slither/detectors/statements/controlled_delegatecall.py +++ b/slither/detectors/statements/controlled_delegatecall.py @@ -1,9 +1,14 @@ +from typing import List + +from slither.analyses.data_dependency.data_dependency import is_tainted +from slither.core.cfg.node import Node +from slither.core.declarations.function_contract import FunctionContract from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import LowLevelCall -from slither.analyses.data_dependency.data_dependency import is_tainted +from slither.utils.output import Output -def controlled_delegatecall(function): +def controlled_delegatecall(function: FunctionContract) -> List[Node]: ret = [] for node in function.nodes: for ir in node.irs: @@ -42,7 +47,7 @@ class ControlledDelegateCall(AbstractDetector): WIKI_RECOMMENDATION = "Avoid using `delegatecall`. Use only trusted destinations." - def _detect(self): + def _detect(self) -> List[Output]: results = [] for contract in self.compilation_unit.contracts_derived: diff --git a/slither/detectors/statements/deprecated_calls.py b/slither/detectors/statements/deprecated_calls.py index f8fc50d329..3d0ca4ba9a 100644 --- a/slither/detectors/statements/deprecated_calls.py +++ b/slither/detectors/statements/deprecated_calls.py @@ -1,14 +1,19 @@ """ Module detecting deprecated standards. """ +from typing import List, Tuple, Union -from slither.core.cfg.node import NodeType +from slither.core.cfg.node import Node, NodeType +from slither.core.declarations.contract import Contract from slither.core.declarations.solidity_variables import ( SolidityVariableComposed, SolidityFunction, ) +from slither.core.expressions.expression import Expression +from slither.core.variables import StateVariable from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import LowLevelCall +from slither.utils.output import Output from slither.visitors.expression.export_values import ExportValues @@ -73,7 +78,9 @@ class DeprecatedStandards(AbstractDetector): DEPRECATED_NODE_TYPES = [(NodeType.THROW, "throw", "revert()")] DEPRECATED_LOW_LEVEL_CALLS = [("callcode", "callcode", "delegatecall")] - def detect_deprecation_in_expression(self, expression): + def detect_deprecation_in_expression( + self, expression: Expression + ) -> List[Tuple[str, str, str]]: """Detects if an expression makes use of any deprecated standards. Returns: @@ -95,13 +102,15 @@ def detect_deprecation_in_expression(self, expression): return results - def detect_deprecated_references_in_node(self, node): + def detect_deprecated_references_in_node( + self, node: Node + ) -> List[Tuple[Union[str, NodeType], str, str]]: """Detects if a node makes use of any deprecated standards. Returns: list of tuple: (detecting_signature, original_text, recommended_text)""" # Define our results list - results = [] + results: List[Tuple[Union[str, NodeType], str, str]] = [] # If this node has an expression, we check the underlying expression. if node.expression: @@ -114,12 +123,24 @@ def detect_deprecated_references_in_node(self, node): return results - def detect_deprecated_references_in_contract(self, contract): + def detect_deprecated_references_in_contract( + self, contract: Contract + ) -> List[ + Union[ + Tuple[StateVariable, List[Tuple[str, str, str]]], + Tuple[Node, List[Tuple[Union[str, NodeType], str, str]]], + ] + ]: """Detects the usage of any deprecated built-in symbols. Returns: list of tuple: (state_variable | node, (detecting_signature, original_text, recommended_text))""" - results = [] + results: List[ + Union[ + Tuple[StateVariable, List[Tuple[str, str, str]]], + Tuple[Node, List[Tuple[Union[str, NodeType], str, str]]], + ] + ] = [] for state_variable in contract.state_variables_declared: if state_variable.expression: @@ -135,22 +156,22 @@ def detect_deprecated_references_in_contract(self, contract): # Loop through each node in this function. for node in function.nodes: # Detect deprecated references in the node. - deprecated_results = self.detect_deprecated_references_in_node(node) + deprecated_results_node = self.detect_deprecated_references_in_node(node) # Detect additional deprecated low-level-calls. for ir in node.irs: if isinstance(ir, LowLevelCall): for dep_llc in self.DEPRECATED_LOW_LEVEL_CALLS: if ir.function_name == dep_llc[0]: - deprecated_results.append(dep_llc) + deprecated_results_node.append(dep_llc) # If we have any results from this iteration, add them to our results list. - if deprecated_results: - results.append((node, deprecated_results)) + if deprecated_results_node: + results.append((node, deprecated_results_node)) return results - def _detect(self): + def _detect(self) -> List[Output]: """Detects if an expression makes use of any deprecated standards. Recursively visit the calls diff --git a/slither/detectors/statements/divide_before_multiply.py b/slither/detectors/statements/divide_before_multiply.py index 47d3ed3c6d..a9de76b407 100644 --- a/slither/detectors/statements/divide_before_multiply.py +++ b/slither/detectors/statements/divide_before_multiply.py @@ -2,12 +2,18 @@ Module detecting possible loss of precision due to divide before multiple """ from collections import defaultdict +from typing import Any, DefaultDict, List, Set, Tuple + +from slither.core.cfg.node import Node +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.slithir.operations import Binary, Assignment, BinaryType, LibraryCall +from slither.slithir.operations import Binary, Assignment, BinaryType, LibraryCall, Operation from slither.slithir.variables import Constant +from slither.utils.output import Output -def is_division(ir): +def is_division(ir: Operation) -> bool: if isinstance(ir, Binary): if ir.type == BinaryType.DIVISION: return True @@ -23,7 +29,7 @@ def is_division(ir): return False -def is_multiplication(ir): +def is_multiplication(ir: Operation) -> bool: if isinstance(ir, Binary): if ir.type == BinaryType.MULTIPLICATION: return True @@ -39,7 +45,7 @@ def is_multiplication(ir): return False -def is_assert(node): +def is_assert(node: Node) -> bool: if node.contains_require_or_assert(): return True # Old Solidity code where using an internal 'assert(bool)' function @@ -50,7 +56,10 @@ def is_assert(node): return False -def _explore(to_explore, f_results, divisions): # pylint: disable=too-many-branches +# pylint: disable=too-many-branches +def _explore( + to_explore: Set[Node], f_results: List[Node], divisions: DefaultDict[Any, Any] +) -> None: explored = set() while to_explore: # pylint: disable=too-many-nested-blocks node = to_explore.pop() @@ -64,8 +73,7 @@ def _explore(to_explore, f_results, divisions): # pylint: disable=too-many-bran node_results = [] for ir in node.irs: - # check for Constant, has its not hashable (TODO: make Constant hashable) - if isinstance(ir, Assignment) and not isinstance(ir.rvalue, Constant): + if isinstance(ir, Assignment): if ir.rvalue in divisions: # Avoid dupplicate. We dont use set so we keep the order of the nodes if node not in divisions[ir.rvalue]: @@ -104,7 +112,9 @@ def _explore(to_explore, f_results, divisions): # pylint: disable=too-many-bran to_explore.add(son) -def detect_divide_before_multiply(contract): +def detect_divide_before_multiply( + contract: Contract, +) -> List[Tuple[FunctionContract, List[Node]]]: """ Detects and returns all nodes with multiplications of division results. :param contract: Contract to detect assignment within. @@ -170,7 +180,7 @@ class DivideBeforeMultiply(AbstractDetector): WIKI_RECOMMENDATION = """Consider ordering multiplication before division.""" - def _detect(self): + def _detect(self) -> List[Output]: """ Detect divisions before multiplications """ diff --git a/slither/detectors/statements/incorrect_strict_equality.py b/slither/detectors/statements/incorrect_strict_equality.py index 9a2234f4f8..bc7b0cebe3 100644 --- a/slither/detectors/statements/incorrect_strict_equality.py +++ b/slither/detectors/statements/incorrect_strict_equality.py @@ -2,7 +2,7 @@ Module detecting dangerous strict equality """ - +from typing import Any, Dict, List, Union from slither.analyses.data_dependency.data_dependency import is_dependent_ssa from slither.core.declarations import Function from slither.core.declarations.function_top_level import FunctionTopLevel @@ -23,6 +23,14 @@ SolidityVariableComposed, SolidityFunction, ) +from slither.core.cfg.node import Node +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract +from slither.slithir.operations.operation import Operation +from slither.slithir.variables.constant import Constant +from slither.slithir.variables.local_variable import LocalIRVariable +from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA +from slither.utils.output import Output class IncorrectStrictEquality(AbstractDetector): @@ -61,11 +69,25 @@ class IncorrectStrictEquality(AbstractDetector): ] @staticmethod - def is_direct_comparison(ir): + def is_direct_comparison(ir: Operation) -> bool: return isinstance(ir, Binary) and ir.type == BinaryType.EQUAL @staticmethod - def is_any_tainted(variables, taints, function) -> bool: + def is_any_tainted( + variables: List[ + Union[ + Constant, + LocalIRVariable, + TemporaryVariableSSA, + SolidityVariableComposed, + SolidityVariable, + ] + ], + taints: List[ + Union[LocalIRVariable, SolidityVariable, SolidityVariableComposed, TemporaryVariableSSA] + ], + function: FunctionContract, + ) -> bool: return any( ( is_dependent_ssa(var, taint, function.contract) @@ -74,7 +96,9 @@ def is_any_tainted(variables, taints, function) -> bool: ) ) - def taint_balance_equalities(self, functions): + def taint_balance_equalities( + self, functions: List[Union[FunctionContract, Any]] + ) -> List[Union[LocalIRVariable, TemporaryVariableSSA, Any]]: taints = [] for func in functions: for node in func.nodes: @@ -105,7 +129,11 @@ def taint_balance_equalities(self, functions): return taints # Retrieve all tainted (node, function) pairs - def tainted_equality_nodes(self, funcs, taints): + def tainted_equality_nodes( + self, + funcs: List[Union[FunctionContract, Any]], + taints: List[Union[LocalIRVariable, TemporaryVariableSSA, Any]], + ) -> Dict[FunctionContract, List[Node]]: results = {} taints += self.sources_taint @@ -124,7 +152,7 @@ def tainted_equality_nodes(self, funcs, taints): return results - def detect_strict_equality(self, contract): + def detect_strict_equality(self, contract: Contract) -> Dict[FunctionContract, List[Node]]: funcs = contract.all_functions_called + contract.modifiers # Taint all BALANCE accesses @@ -135,7 +163,7 @@ def detect_strict_equality(self, contract): return results - def _detect(self): + def _detect(self) -> List[Output]: results = [] for c in self.compilation_unit.contracts_derived: diff --git a/slither/detectors/statements/mapping_deletion.py b/slither/detectors/statements/mapping_deletion.py index 2515eacf6c..59882cc961 100644 --- a/slither/detectors/statements/mapping_deletion.py +++ b/slither/detectors/statements/mapping_deletion.py @@ -1,11 +1,16 @@ """ Detect deletion on structure containing a mapping """ +from typing import List, Tuple +from slither.core.cfg.node import Node from slither.core.declarations import Structure +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract from slither.core.solidity_types import MappingType, UserDefinedType from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import Delete +from slither.utils.output import Output class MappingDeletionDetection(AbstractDetector): @@ -45,13 +50,15 @@ class MappingDeletionDetection(AbstractDetector): ) @staticmethod - def detect_mapping_deletion(contract): + def detect_mapping_deletion( + contract: Contract, + ) -> List[Tuple[FunctionContract, Structure, Node]]: """Detect deletion on structure containing a mapping Returns: list (function, structure, node) """ - ret = [] + ret: List[Tuple[FunctionContract, Structure, Node]] = [] # pylint: disable=too-many-nested-blocks for f in contract.functions: for node in f.nodes: @@ -66,7 +73,7 @@ def detect_mapping_deletion(contract): ret.append((f, st, node)) return ret - def _detect(self): + def _detect(self) -> List[Output]: """Detect mapping deletion Returns: diff --git a/slither/detectors/statements/redundant_statements.py b/slither/detectors/statements/redundant_statements.py index 023d326ecd..7e72231342 100644 --- a/slither/detectors/statements/redundant_statements.py +++ b/slither/detectors/statements/redundant_statements.py @@ -1,11 +1,14 @@ """ Module detecting redundant statements. """ +from typing import List -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.core.cfg.node import NodeType +from slither.core.cfg.node import Node, NodeType +from slither.core.declarations.contract import Contract from slither.core.expressions.elementary_type_name_expression import ElementaryTypeNameExpression from slither.core.expressions.identifier import Identifier +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output class RedundantStatements(AbstractDetector): @@ -50,7 +53,7 @@ class RedundantStatements(AbstractDetector): # This is a disallowed list of tuple (node.type, type(node.expression)) REDUNDANT_TOP_LEVEL_EXPRESSIONS = (ElementaryTypeNameExpression, Identifier) - def detect_redundant_statements_contract(self, contract): + def detect_redundant_statements_contract(self, contract: Contract) -> List[Node]: """Detects the usage of redundant statements in a contract. Returns: @@ -70,7 +73,7 @@ def detect_redundant_statements_contract(self, contract): return results - def _detect(self): + def _detect(self) -> List[Output]: """Detect redundant statements Recursively visit the calls diff --git a/slither/detectors/statements/too_many_digits.py b/slither/detectors/statements/too_many_digits.py index 6e4cc4cd91..239efa4bed 100644 --- a/slither/detectors/statements/too_many_digits.py +++ b/slither/detectors/statements/too_many_digits.py @@ -3,13 +3,18 @@ """ import re +from typing import List + +from slither.core.cfg.node import Node +from slither.core.declarations.function_contract import FunctionContract from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.variables import Constant +from slither.utils.output import Output _HEX_ADDRESS_REGEXP = re.compile("(0[xX])?[0-9a-fA-F]{40}") -def is_hex_address(value) -> bool: +def is_hex_address(value: str) -> bool: """ Checks if the given string of text type is an address in hexadecimal encoded form. """ @@ -57,7 +62,7 @@ class TooManyDigits(AbstractDetector): # endregion wiki_recommendation @staticmethod - def _detect_too_many_digits(f): + def _detect_too_many_digits(f: FunctionContract) -> List[Node]: ret = [] for node in f.nodes: # each node contains a list of IR instruction @@ -73,7 +78,7 @@ def _detect_too_many_digits(f): ret.append(node) return ret - def _detect(self): + def _detect(self) -> List[Output]: results = [] # iterate over all contracts diff --git a/slither/detectors/statements/tx_origin.py b/slither/detectors/statements/tx_origin.py index 485eeaf136..34f8173d53 100644 --- a/slither/detectors/statements/tx_origin.py +++ b/slither/detectors/statements/tx_origin.py @@ -1,8 +1,13 @@ """ Module detecting usage of `tx.origin` in a conditional node """ +from typing import List, Tuple +from slither.core.cfg.node import Node +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_contract import FunctionContract from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output class TxOrigin(AbstractDetector): @@ -38,7 +43,7 @@ class TxOrigin(AbstractDetector): WIKI_RECOMMENDATION = "Do not use `tx.origin` for authorization." @staticmethod - def _contains_incorrect_tx_origin_use(node): + def _contains_incorrect_tx_origin_use(node: Node) -> bool: """ Check if the node reads tx.origin and doesn't read msg.sender Avoid the FP due to (msg.sender == tx.origin) @@ -52,7 +57,7 @@ def _contains_incorrect_tx_origin_use(node): ) return False - def detect_tx_origin(self, contract): + def detect_tx_origin(self, contract: Contract) -> List[Tuple[FunctionContract, List[Node]]]: ret = [] for f in contract.functions: @@ -67,7 +72,7 @@ def detect_tx_origin(self, contract): ret.append((f, bad_tx_nodes)) return ret - def _detect(self): + def _detect(self) -> List[Output]: """Detect the functions that use tx.origin in a conditional node""" results = [] for c in self.contracts: diff --git a/slither/detectors/statements/type_based_tautology.py b/slither/detectors/statements/type_based_tautology.py index 0129ad03f8..9edb1f53ec 100644 --- a/slither/detectors/statements/type_based_tautology.py +++ b/slither/detectors/statements/type_based_tautology.py @@ -1,14 +1,19 @@ """ Module detecting tautologies and contradictions based on types in comparison operations over integers """ +from typing import List, Set, Tuple +from slither.core.cfg.node import Node +from slither.core.declarations import Function +from slither.core.declarations.contract import Contract +from slither.core.solidity_types.elementary_type import Int, Uint from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import Binary, BinaryType from slither.slithir.variables import Constant -from slither.core.solidity_types.elementary_type import Int, Uint +from slither.utils.output import Output -def typeRange(t): +def typeRange(t: str) -> Tuple[int, int]: bits = int(t.split("int")[1]) if t in Uint: return 0, (2**bits) - 1 @@ -18,7 +23,7 @@ def typeRange(t): return None -def _detect_tautology_or_contradiction(low, high, cval, op): +def _detect_tautology_or_contradiction(low: int, high: int, cval: int, op: BinaryType) -> bool: """ Return true if "[low high] op cval " is always true or always false :param low: @@ -110,7 +115,7 @@ class TypeBasedTautology(AbstractDetector): BinaryType.LESS_EQUAL: BinaryType.GREATER_EQUAL, } - def detect_type_based_tautologies(self, contract): + def detect_type_based_tautologies(self, contract: Contract) -> List[Tuple[Function, Set[Node]]]: """ Detects and returns all nodes with tautology/contradiction comparisons (based on type alone). :param contract: Contract to detect assignment within. @@ -118,7 +123,7 @@ def detect_type_based_tautologies(self, contract): """ # Create our result set. - results = [] + results: List[Tuple[Function, Set[Node]]] = [] allInts = Int + Uint # Loop for each function and modifier. @@ -151,7 +156,7 @@ def detect_type_based_tautologies(self, contract): # Return the resulting set of nodes with tautologies and contradictions return results - def _detect(self): + def _detect(self) -> List[Output]: """ Detect tautological (or contradictory) comparisons """ diff --git a/slither/detectors/statements/unary.py b/slither/detectors/statements/unary.py index 019c80e29c..5bb8d9c3c6 100644 --- a/slither/detectors/statements/unary.py +++ b/slither/detectors/statements/unary.py @@ -1,14 +1,17 @@ """ Module detecting the incorrect use of unary expressions """ +from typing import List +from slither.core.expressions.assignment_operation import AssignmentOperation +from slither.core.expressions.unary_operation import UnaryOperationType, UnaryOperation from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output from slither.visitors.expression.expression import ExpressionVisitor -from slither.core.expressions.unary_operation import UnaryOperationType, UnaryOperation class InvalidUnaryExpressionDetector(ExpressionVisitor): - def _post_assignement_operation(self, expression): + def _post_assignement_operation(self, expression: AssignmentOperation) -> None: if isinstance(expression.expression_right, UnaryOperation): if expression.expression_right.type == UnaryOperationType.PLUS_PRE: # This is defined in ExpressionVisitor but pylint @@ -18,7 +21,7 @@ def _post_assignement_operation(self, expression): class InvalidUnaryStateVariableDetector(ExpressionVisitor): - def _post_unary_operation(self, expression): + def _post_unary_operation(self, expression: UnaryOperation) -> None: if expression.type == UnaryOperationType.PLUS_PRE: # This is defined in ExpressionVisitor but pylint # Seems to think its not @@ -60,7 +63,7 @@ class IncorrectUnaryExpressionDetection(AbstractDetector): WIKI_RECOMMENDATION = "Remove the unary expression." - def _detect(self): + def _detect(self) -> List[Output]: """ Detect the incorrect use of unary expressions """ diff --git a/slither/detectors/statements/unprotected_upgradeable.py b/slither/detectors/statements/unprotected_upgradeable.py index 25be6a5ae5..1adf495407 100644 --- a/slither/detectors/statements/unprotected_upgradeable.py +++ b/slither/detectors/statements/unprotected_upgradeable.py @@ -4,6 +4,7 @@ from slither.core.declarations.contract import Contract from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import LowLevelCall, SolidityCall +from slither.utils.output import Output def _can_be_destroyed(contract: Contract) -> List[Function]: @@ -87,7 +88,7 @@ class UnprotectedUpgradeable(AbstractDetector): """Add a constructor to ensure `initialize` cannot be called on the logic contract.""" ) - def _detect(self): + def _detect(self) -> List[Output]: results = [] for contract in self.compilation_unit.contracts_derived: diff --git a/slither/detectors/statements/write_after_write.py b/slither/detectors/statements/write_after_write.py index ea654f79d0..5b2e29925c 100644 --- a/slither/detectors/statements/write_after_write.py +++ b/slither/detectors/statements/write_after_write.py @@ -15,9 +15,10 @@ ) from slither.slithir.variables import ReferenceVariable, TemporaryVariable, TupleVariable from slither.slithir.variables.variable import SlithIRVariable +from slither.utils.output import Output -def _remove_states(written: Dict[Variable, Node]): +def _remove_states(written: Dict[Variable, Node]) -> None: for key in list(written.keys()): if isinstance(key, StateVariable): del written[key] @@ -27,7 +28,7 @@ def _handle_ir( ir: Operation, written: Dict[Variable, Node], ret: List[Tuple[Variable, Node, Node]], -): +) -> None: if isinstance(ir, (HighLevelCall, InternalDynamicCall, LowLevelCall)): _remove_states(written) @@ -73,7 +74,7 @@ def _detect_write_after_write( explored: Set[Node], written: Dict[Variable, Node], ret: List[Tuple[Variable, Node, Node]], -): +) -> None: if node in explored: return @@ -121,7 +122,7 @@ class WriteAfterWrite(AbstractDetector): WIKI_RECOMMENDATION = """Fix or remove the writes.""" - def _detect(self): + def _detect(self) -> List[Output]: results = [] for contract in self.compilation_unit.contracts_derived: diff --git a/slither/detectors/variables/function_init_state_variables.py b/slither/detectors/variables/function_init_state_variables.py index 081eb9f8be..e35cfe351c 100644 --- a/slither/detectors/variables/function_init_state_variables.py +++ b/slither/detectors/variables/function_init_state_variables.py @@ -1,14 +1,17 @@ """ Module detecting state variables initializing from an immediate function call (prior to constructor run). """ +from typing import List -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification -from slither.visitors.expression.export_values import ExportValues +from slither.core.declarations.contract import Contract from slither.core.declarations.function import Function from slither.core.variables.state_variable import StateVariable +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output +from slither.visitors.expression.export_values import ExportValues -def detect_function_init_state_vars(contract): +def detect_function_init_state_vars(contract: Contract) -> List[StateVariable]: """ Detect any state variables that are initialized from an immediate function call (prior to constructor run). :param contract: The contract to detect state variable definitions for. @@ -87,7 +90,7 @@ class FunctionInitializedState(AbstractDetector): WIKI_RECOMMENDATION = "Remove any initialization of state variables via non-constant state variables or function calls. If variables must be set upon contract deployment, locate initialization in the constructor instead." - def _detect(self): + def _detect(self) -> List[Output]: """ Detect state variables defined from an immediate function call (pre-contract deployment). diff --git a/slither/detectors/variables/predeclaration_usage_local.py b/slither/detectors/variables/predeclaration_usage_local.py index 8e36b19a3b..177035ef43 100644 --- a/slither/detectors/variables/predeclaration_usage_local.py +++ b/slither/detectors/variables/predeclaration_usage_local.py @@ -1,8 +1,18 @@ """ Module detecting any path leading to usage of a local variable before it is declared. """ +from typing import List, Set, Tuple -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.cfg.node import Node +from slither.core.declarations import Function +from slither.core.declarations.contract import Contract +from slither.core.variables.local_variable import LocalVariable +from slither.detectors.abstract_detector import ( + AbstractDetector, + DetectorClassification, + ALL_SOLC_VERSIONS_04, +) +from slither.utils.output import Output class PredeclarationUsageLocal(AbstractDetector): @@ -48,7 +58,15 @@ class PredeclarationUsageLocal(AbstractDetector): WIKI_RECOMMENDATION = "Move all variable declarations prior to any usage of the variable, and ensure that reaching a variable declaration does not depend on some conditional if it is used unconditionally." - def detect_predeclared_local_usage(self, node, results, already_declared, visited): + VULNERABLE_SOLC_VERSIONS = ALL_SOLC_VERSIONS_04 + + def detect_predeclared_local_usage( + self, + node: Node, + results: List[Tuple[Node, LocalVariable]], + already_declared: Set[LocalVariable], + visited: Set[Node], + ) -> None: """ Detects if a given node uses a variable prior to declaration in any code path. :param node: The node to initiate the scan from (searches recursively through all sons) @@ -87,7 +105,9 @@ def detect_predeclared_local_usage(self, node, results, already_declared, visite for son in node.sons: self.detect_predeclared_local_usage(son, results, already_declared, visited) - def detect_predeclared_in_contract(self, contract): + def detect_predeclared_in_contract( + self, contract: Contract + ) -> List[Tuple[Function, List[Tuple[Node, LocalVariable]]]]: """ Detects and returns all nodes in a contract which use a variable before it is declared. :param contract: Contract to detect pre-declaration usage of locals within. @@ -95,11 +115,11 @@ def detect_predeclared_in_contract(self, contract): """ # Create our result set. - results = [] + results: List[Tuple[Function, List[Tuple[Node, LocalVariable]]]] = [] # Loop for each function and modifier's nodes and analyze for predeclared local variable usage. for function in contract.functions_and_modifiers_declared: - predeclared_usage = [] + predeclared_usage: List[Tuple[Node, LocalVariable]] = [] if function.nodes: self.detect_predeclared_local_usage( function.nodes[0], @@ -113,7 +133,7 @@ def detect_predeclared_in_contract(self, contract): # Return the resulting set of nodes which set array length. return results - def _detect(self): + def _detect(self) -> List[Output]: """ Detect usage of a local variable before it is declared. """ diff --git a/slither/detectors/variables/similar_variables.py b/slither/detectors/variables/similar_variables.py index bab2d0accd..d0a15aaab7 100644 --- a/slither/detectors/variables/similar_variables.py +++ b/slither/detectors/variables/similar_variables.py @@ -3,8 +3,12 @@ Do not check contract inheritance """ import difflib +from typing import List, Set, Tuple +from slither.core.declarations.contract import Contract +from slither.core.variables.local_variable import LocalVariable from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output class SimilarVarsDetection(AbstractDetector): @@ -27,7 +31,7 @@ class SimilarVarsDetection(AbstractDetector): WIKI_RECOMMENDATION = "Prevent variables from having similar names." @staticmethod - def similar(seq1, seq2): + def similar(seq1: str, seq2: str) -> bool: """Test the name similarity Two name are similar if difflib.SequenceMatcher on the lowercase @@ -46,7 +50,7 @@ def similar(seq1, seq2): return ret @staticmethod - def detect_sim(contract): + def detect_sim(contract: Contract) -> Set[Tuple[LocalVariable, LocalVariable]]: """Detect variables with similar name Returns: @@ -69,7 +73,7 @@ def detect_sim(contract): return set(ret) - def _detect(self): + def _detect(self) -> List[Output]: """Detect similar variables name Returns: diff --git a/slither/detectors/variables/unchanged_state_variables.py b/slither/detectors/variables/unchanged_state_variables.py index 0dccb6d1cf..f12cc57849 100644 --- a/slither/detectors/variables/unchanged_state_variables.py +++ b/slither/detectors/variables/unchanged_state_variables.py @@ -69,7 +69,7 @@ class UnchangedStateVariables: Find state variables that could be declared as constant or immutable (not written after deployment). """ - def __init__(self, compilation_unit: SlitherCompilationUnit): + def __init__(self, compilation_unit: SlitherCompilationUnit) -> None: self.compilation_unit = compilation_unit self._constant_candidates: List[StateVariable] = [] self._immutable_candidates: List[StateVariable] = [] @@ -84,7 +84,7 @@ def constant_candidates(self) -> List[StateVariable]: """Return the constant candidates""" return self._constant_candidates - def detect(self): + def detect(self) -> None: """Detect state variables that could be constant or immutable""" for c in self.compilation_unit.contracts_derived: variables = [] @@ -118,8 +118,9 @@ def detect(self): self.constant_candidates.append(v) elif ( - v in constructor_variables_written or v in variables_initialized - ) and version.parse(self.compilation_unit.solc_version) >= version.parse( - "0.6.5" + not v.type.is_dynamic + and version.parse(self.compilation_unit.solc_version) + >= version.parse("0.6.5") + and (v in constructor_variables_written or v in variables_initialized) ): self.immutable_candidates.append(v) diff --git a/slither/detectors/variables/uninitialized_local_variables.py b/slither/detectors/variables/uninitialized_local_variables.py index 7f7cb76e04..759691d504 100644 --- a/slither/detectors/variables/uninitialized_local_variables.py +++ b/slither/detectors/variables/uninitialized_local_variables.py @@ -4,8 +4,12 @@ Recursively explore the CFG to only report uninitialized local variables that are read before being written """ +from typing import List +from slither.core.cfg.node import Node +from slither.core.declarations.function_contract import FunctionContract from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output class UninitializedLocalVars(AbstractDetector): @@ -37,7 +41,9 @@ class UninitializedLocalVars(AbstractDetector): key = "UNINITIALIZEDLOCAL" - def _detect_uninitialized(self, function, node, visited): + def _detect_uninitialized( + self, function: FunctionContract, node: Node, visited: List[Node] + ) -> None: if node in visited: return @@ -73,7 +79,7 @@ def _detect_uninitialized(self, function, node, visited): for son in node.sons: self._detect_uninitialized(function, son, visited) - def _detect(self): + def _detect(self) -> List[Output]: """Detect uninitialized local variables Recursively visit the calls diff --git a/slither/detectors/variables/uninitialized_state_variables.py b/slither/detectors/variables/uninitialized_state_variables.py index baf1b22184..0fbb73b5dc 100644 --- a/slither/detectors/variables/uninitialized_state_variables.py +++ b/slither/detectors/variables/uninitialized_state_variables.py @@ -8,10 +8,16 @@ Only analyze "leaf" contracts (contracts that are not inherited by another contract) """ +from typing import List, Tuple +from slither.core.declarations import Function +from slither.core.declarations.contract import Contract +from slither.core.variables import Variable +from slither.core.variables.state_variable import StateVariable from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations import InternalCall, LibraryCall from slither.slithir.variables import ReferenceVariable +from slither.utils.output import Output class UninitializedStateVarsDetection(AbstractDetector): @@ -51,7 +57,7 @@ class UninitializedStateVarsDetection(AbstractDetector): # endregion wiki_recommendation @staticmethod - def _written_variables(contract): + def _written_variables(contract: Contract) -> List[StateVariable]: ret = [] # pylint: disable=too-many-nested-blocks for f in contract.all_functions_called + contract.modifiers: @@ -88,8 +94,8 @@ def _variable_written_in_proxy(self): self.__variables_written_in_proxy = list({v.name for v in variables_written_in_proxy}) return self.__variables_written_in_proxy - def _written_variables_in_proxy(self, contract): - variables = [] + def _written_variables_in_proxy(self, contract: Contract) -> List[StateVariable]: + variables: List[StateVariable] = [] if contract.is_upgradeable: variables_name_written_in_proxy = self._variable_written_in_proxy() if variables_name_written_in_proxy: @@ -97,18 +103,20 @@ def _written_variables_in_proxy(self, contract): contract.get_state_variable_from_name(v) for v in variables_name_written_in_proxy ] - variables_in_contract = [v for v in variables_in_contract if v] - variables += variables_in_contract + variables += [v for v in variables_in_contract if v] return list(set(variables)) @staticmethod - def _read_variables(contract): + def _read_variables(contract: Contract) -> List[StateVariable]: ret = [] - for f in contract.all_functions_called + contract.modifiers: - ret += f.state_variables_read + for f in contract.all_functions_called: + if isinstance(f, Function): + ret += f.state_variables_read + for m in contract.modifiers: + ret += m.state_variables_read return ret - def _detect_uninitialized(self, contract): + def _detect_uninitialized(self, contract: Contract) -> List[Tuple[Variable, List[Function]]]: written_variables = self._written_variables(contract) written_variables += self._written_variables_in_proxy(contract) read_variables = self._read_variables(contract) @@ -120,7 +128,7 @@ def _detect_uninitialized(self, contract): and variable in read_variables ] - def _detect(self): + def _detect(self) -> List[Output]: """Detect uninitialized state variables Recursively visit the calls diff --git a/slither/detectors/variables/uninitialized_storage_variables.py b/slither/detectors/variables/uninitialized_storage_variables.py index a0c35d80df..9caa5b88fc 100644 --- a/slither/detectors/variables/uninitialized_storage_variables.py +++ b/slither/detectors/variables/uninitialized_storage_variables.py @@ -4,8 +4,12 @@ Recursively explore the CFG to only report uninitialized storage variables that are written before being read """ +from typing import List +from slither.core.cfg.node import Node +from slither.core.declarations.function_contract import FunctionContract from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.utils.output import Output class UninitializedStorageVars(AbstractDetector): @@ -45,7 +49,9 @@ class UninitializedStorageVars(AbstractDetector): # node.context[self.key] contains the uninitialized storage variables key = "UNINITIALIZEDSTORAGE" - def _detect_uninitialized(self, function, node, visited): + def _detect_uninitialized( + self, function: FunctionContract, node: Node, visited: List[Node] + ) -> None: if node in visited: return @@ -81,7 +87,7 @@ def _detect_uninitialized(self, function, node, visited): for son in node.sons: self._detect_uninitialized(function, son, visited) - def _detect(self): + def _detect(self) -> List[Output]: """Detect uninitialized storage variables Recursively visit the calls @@ -97,9 +103,11 @@ def _detect(self): for contract in self.compilation_unit.contracts: for function in contract.functions: if function.is_implemented and function.entry_point: + locals_except_params = set(function.variables) - set(function.parameters) uninitialized_storage_variables = [ - v for v in function.local_variables if v.is_storage and v.uninitialized + v for v in locals_except_params if v.is_storage and v.uninitialized ] + function.entry_point.context[self.key] = uninitialized_storage_variables self._detect_uninitialized(function, function.entry_point, []) diff --git a/slither/detectors/variables/unused_state_variables.py b/slither/detectors/variables/unused_state_variables.py index 71cecbfbd5..d542f67d30 100644 --- a/slither/detectors/variables/unused_state_variables.py +++ b/slither/detectors/variables/unused_state_variables.py @@ -1,15 +1,19 @@ """ Module detecting unused state variables """ +from typing import List, Optional + from slither.core.compilation_unit import SlitherCompilationUnit -from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification +from slither.core.declarations.contract import Contract from slither.core.solidity_types import ArrayType -from slither.visitors.expression.export_values import ExportValues from slither.core.variables.state_variable import StateVariable +from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.formatters.variables.unused_state_variables import custom_format +from slither.utils.output import Output +from slither.visitors.expression.export_values import ExportValues -def detect_unused(contract): +def detect_unused(contract: Contract) -> Optional[List[StateVariable]]: if contract.is_signature_only(): return None # Get all the variables read in all the functions and modifiers @@ -54,7 +58,7 @@ class UnusedStateVars(AbstractDetector): WIKI_EXPLOIT_SCENARIO = "" WIKI_RECOMMENDATION = "Remove unused state variables." - def _detect(self): + def _detect(self) -> List[Output]: """Detect unused state variables""" results = [] for c in self.compilation_unit.contracts_derived: diff --git a/slither/detectors/variables/var_read_using_this.py b/slither/detectors/variables/var_read_using_this.py index 3d9f204c26..b224f8c17d 100644 --- a/slither/detectors/variables/var_read_using_this.py +++ b/slither/detectors/variables/var_read_using_this.py @@ -1,8 +1,10 @@ from typing import List + from slither.core.cfg.node import Node from slither.core.declarations import Function, SolidityVariable from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.slithir.operations.high_level_call import HighLevelCall +from slither.utils.output import Output class VarReadUsingThis(AbstractDetector): @@ -28,7 +30,7 @@ class VarReadUsingThis(AbstractDetector): WIKI_RECOMMENDATION = "Read the variable directly from storage instead of calling the contract." - def _detect(self): + def _detect(self) -> List[Output]: results = [] for c in self.contracts: for func in c.functions: diff --git a/slither/printers/guidance/echidna.py b/slither/printers/guidance/echidna.py index 95d113a84b..b160dd0e6f 100644 --- a/slither/printers/guidance/echidna.py +++ b/slither/printers/guidance/echidna.py @@ -4,7 +4,7 @@ from slither.analyses.data_dependency.data_dependency import is_dependent from slither.core.cfg.node import Node -from slither.core.declarations import Function +from slither.core.declarations import Enum, Function from slither.core.declarations.solidity_variables import ( SolidityVariableComposed, SolidityFunction, @@ -12,6 +12,7 @@ ) from slither.core.expressions import NewContract from slither.core.slither_core import SlitherCore +from slither.core.solidity_types import TypeAlias from slither.core.variables.state_variable import StateVariable from slither.core.variables.variable import Variable from slither.printers.abstract_printer import AbstractPrinter @@ -30,6 +31,7 @@ ) from slither.slithir.operations.binary import Binary from slither.slithir.variables import Constant +from slither.utils.output import Output from slither.visitors.expression.constants_folding import ConstantFolding @@ -168,7 +170,7 @@ def _extract_constants_from_irs( # pylint: disable=too-many-branches,too-many-n all_cst_used: List[ConstantValue], all_cst_used_in_binary: Dict[str, List[ConstantValue]], context_explored: Set[Node], -): +) -> None: for ir in irs: if isinstance(ir, Binary): for r in ir.read: @@ -183,8 +185,23 @@ def _extract_constants_from_irs( # pylint: disable=too-many-branches,too-many-n all_cst_used.append(ConstantValue(str(cst.value), str(type_))) if isinstance(ir, TypeConversion): if isinstance(ir.variable, Constant): - all_cst_used.append(ConstantValue(str(ir.variable.value), str(ir.type))) + if isinstance(ir.type, TypeAlias): + value_type = ir.type.type + else: + value_type = ir.type + all_cst_used.append(ConstantValue(str(ir.variable.value), str(value_type))) continue + if ( + isinstance(ir, Member) + and isinstance(ir.variable_left, Enum) + and isinstance(ir.variable_right, Constant) + ): + # enums are constant values + try: + internal_num = ir.variable_left.values.index(ir.variable_right.value) + all_cst_used.append(ConstantValue(str(internal_num), "uint256")) + except ValueError: # index could fail; should never happen in working solidity code + pass for r in ir.read: # Do not report struct_name in a.struct_name if isinstance(ir, Member): @@ -364,7 +381,7 @@ class Echidna(AbstractPrinter): WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#echidna" - def output(self, filename): # pylint: disable=too-many-locals + def output(self, filename: str) -> Output: # pylint: disable=too-many-locals """ Output the inheritance relation diff --git a/slither/printers/summary/constructor_calls.py b/slither/printers/summary/constructor_calls.py index 4593800b94..665c765469 100644 --- a/slither/printers/summary/constructor_calls.py +++ b/slither/printers/summary/constructor_calls.py @@ -1,24 +1,22 @@ """ Module printing summary of the contract """ +from slither.core.declarations import Function from slither.core.source_mapping.source_mapping import Source from slither.printers.abstract_printer import AbstractPrinter from slither.utils import output +def _get_source_code(cst: Function) -> str: + src_mapping: Source = cst.source_mapping + return " " * src_mapping.starting_column + src_mapping.content + + class ConstructorPrinter(AbstractPrinter): WIKI = "https://github.com/crytic/slither/wiki/Printer-documentation#constructor-calls" ARGUMENT = "constructor-calls" HELP = "Print the constructors executed" - def _get_soruce_code(self, cst): - src_mapping: Source = cst.source_mapping - content = self.slither.source_code[src_mapping.filename.absolute] - start = src_mapping.start - end = src_mapping.start + src_mapping.length - initial_space = src_mapping.starting_column - return " " * initial_space + content[start:end] - def output(self, _filename): info = "" for contract in self.slither.contracts_derived: @@ -27,12 +25,12 @@ def output(self, _filename): cst = contract.constructors_declared if cst: stack_name.append(contract.name) - stack_definition.append(self._get_soruce_code(cst)) + stack_definition.append(_get_source_code(cst)) for inherited_contract in contract.inheritance: cst = inherited_contract.constructors_declared if cst: stack_name.append(inherited_contract.name) - stack_definition.append(self._get_soruce_code(cst)) + stack_definition.append(_get_source_code(cst)) if len(stack_name) > 0: diff --git a/slither/printers/summary/declaration.py b/slither/printers/summary/declaration.py index 5888a1f00f..529aba5f08 100644 --- a/slither/printers/summary/declaration.py +++ b/slither/printers/summary/declaration.py @@ -20,37 +20,37 @@ def output(self, _filename): txt += "\n# Contracts\n" for contract in compilation_unit.contracts: txt += f"# {contract.name}\n" - txt += f"\t- Declaration: {get_definition(contract, compilation_unit.core.crytic_compile).to_detailled_str()}\n" - txt += f"\t- Implementation: {get_implementation(contract).to_detailled_str()}\n" + txt += f"\t- Declaration: {get_definition(contract, compilation_unit.core.crytic_compile).to_detailed_str()}\n" + txt += f"\t- Implementation: {get_implementation(contract).to_detailed_str()}\n" txt += ( - f"\t- References: {[x.to_detailled_str() for x in get_references(contract)]}\n" + f"\t- References: {[x.to_detailed_str() for x in get_references(contract)]}\n" ) txt += "\n\t## Function\n" for func in contract.functions: txt += f"\t\t- {func.canonical_name}\n" - txt += f"\t\t\t- Declaration: {get_definition(func, compilation_unit.core.crytic_compile).to_detailled_str()}\n" - txt += ( - f"\t\t\t- Implementation: {get_implementation(func).to_detailled_str()}\n" - ) - txt += f"\t\t\t- References: {[x.to_detailled_str() for x in get_references(func)]}\n" + txt += f"\t\t\t- Declaration: {get_definition(func, compilation_unit.core.crytic_compile).to_detailed_str()}\n" + txt += f"\t\t\t- Implementation: {get_implementation(func).to_detailed_str()}\n" + txt += f"\t\t\t- References: {[x.to_detailed_str() for x in get_references(func)]}\n" txt += "\n\t## State variables\n" for var in contract.state_variables: txt += f"\t\t- {var.name}\n" - txt += f"\t\t\t- Declaration: {get_definition(var, compilation_unit.core.crytic_compile).to_detailled_str()}\n" - txt += f"\t\t\t- Implementation: {get_implementation(var).to_detailled_str()}\n" - txt += f"\t\t\t- References: {[x.to_detailled_str() for x in get_references(var)]}\n" + txt += f"\t\t\t- Declaration: {get_definition(var, compilation_unit.core.crytic_compile).to_detailed_str()}\n" + txt += f"\t\t\t- Implementation: {get_implementation(var).to_detailed_str()}\n" + txt += f"\t\t\t- References: {[x.to_detailed_str() for x in get_references(var)]}\n" txt += "\n\t## Structures\n" for st in contract.structures: txt += f"\t\t- {st.name}\n" - txt += f"\t\t\t- Declaration: {get_definition(st, compilation_unit.core.crytic_compile).to_detailled_str()}\n" - txt += f"\t\t\t- Implementation: {get_implementation(st).to_detailled_str()}\n" - txt += f"\t\t\t- References: {[x.to_detailled_str() for x in get_references(st)]}\n" + txt += f"\t\t\t- Declaration: {get_definition(st, compilation_unit.core.crytic_compile).txt}\n" + txt += f"\t\t\t- Implementation: {get_implementation(st).to_detailed_str()}\n" + txt += ( + f"\t\t\t- References: {[x.to_detailed_str() for x in get_references(st)]}\n" + ) self.info(txt) res = self.generate_output(txt) diff --git a/slither/printers/summary/evm.py b/slither/printers/summary/evm.py index 660d912042..0e5ce58d78 100644 --- a/slither/printers/summary/evm.py +++ b/slither/printers/summary/evm.py @@ -21,8 +21,12 @@ def _extract_evm_info(slither): CFG = load_evm_cfg_builder() for contract in slither.contracts_derived: - contract_bytecode_runtime = contract.scope.bytecode_runtime(contract.name) - contract_srcmap_runtime = contract.scope.srcmap_runtime(contract.name) + contract_bytecode_runtime = contract.file_scope.bytecode_runtime( + contract.compilation_unit.crytic_compile_compilation_unit, contract.name + ) + contract_srcmap_runtime = contract.file_scope.srcmap_runtime( + contract.compilation_unit.crytic_compile_compilation_unit, contract.name + ) cfg = CFG(contract_bytecode_runtime) evm_info["cfg", contract.name] = cfg evm_info["mapping", contract.name] = generate_source_to_evm_ins_mapping( @@ -32,8 +36,12 @@ def _extract_evm_info(slither): contract.source_mapping.filename.absolute, ) - contract_bytecode_init = contract.scope.bytecode_init(contract.name) - contract_srcmap_init = contract.scope.srcmap_init(contract.name) + contract_bytecode_init = contract.file_scope.bytecode_init( + contract.compilation_unit.crytic_compile_compilation_unit, contract.name + ) + contract_srcmap_init = contract.file_scope.srcmap_init( + contract.compilation_unit.crytic_compile_compilation_unit, contract.name + ) cfg_init = CFG(contract_bytecode_init) evm_info["cfg_init", contract.name] = cfg_init diff --git a/slither/printers/summary/function.py b/slither/printers/summary/function.py index b9353ce252..7f1633865a 100644 --- a/slither/printers/summary/function.py +++ b/slither/printers/summary/function.py @@ -48,6 +48,7 @@ def output(self, _filename): # pylint: disable=too-many-locals "Write", "Internal Calls", "External Calls", + "Cyclomatic Complexity", ] ) for ( @@ -59,6 +60,7 @@ def output(self, _filename): # pylint: disable=too-many-locals write, internal_calls, external_calls, + cyclomatic_complexity, ) in func_summaries: read = self._convert(sorted(read)) write = self._convert(sorted(write)) @@ -73,6 +75,7 @@ def output(self, _filename): # pylint: disable=too-many-locals write, internal_calls, external_calls, + cyclomatic_complexity, ] ) txt += "\n \n" + str(table) @@ -84,6 +87,7 @@ def output(self, _filename): # pylint: disable=too-many-locals "Write", "Internal Calls", "External Calls", + "Cyclomatic Complexity", ] ) for ( @@ -95,12 +99,23 @@ def output(self, _filename): # pylint: disable=too-many-locals write, internal_calls, external_calls, + cyclomatic_complexity, ) in modif_summaries: read = self._convert(sorted(read)) write = self._convert(sorted(write)) internal_calls = self._convert(sorted(internal_calls)) external_calls = self._convert(sorted(external_calls)) - table.add_row([f_name, visi, read, write, internal_calls, external_calls]) + table.add_row( + [ + f_name, + visi, + read, + write, + internal_calls, + external_calls, + cyclomatic_complexity, + ] + ) txt += "\n\n" + str(table) txt += "\n" self.info(txt) diff --git a/slither/printers/summary/slithir.py b/slither/printers/summary/slithir.py index a1b95d8f8f..dea3d3126b 100644 --- a/slither/printers/summary/slithir.py +++ b/slither/printers/summary/slithir.py @@ -54,7 +54,7 @@ def output(self, _filename): txt += f"\tModifier {modifier.canonical_name}\n" txt += _print_function(modifier) if compilation_unit.functions_top_level: - txt += "Top level functions" + txt += "Top level functions\n" for function in compilation_unit.functions_top_level: txt += f"\tFunction {function.canonical_name}\n" txt += _print_function(function) diff --git a/slither/slither.py b/slither/slither.py index e08355a4c3..ab36678713 100644 --- a/slither/slither.py +++ b/slither/slither.py @@ -1,6 +1,5 @@ import logging - -from typing import Union, List, ValuesView, Type, Dict +from typing import Union, List, ValuesView, Type, Dict, Optional from crytic_compile import CryticCompile, InvalidCompilation @@ -12,6 +11,7 @@ from slither.exceptions import SlitherError from slither.printers.abstract_printer import AbstractPrinter from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc +from slither.utils.output import Output logger = logging.getLogger("Slither") logging.basicConfig() @@ -49,7 +49,7 @@ def _update_file_scopes(candidates: ValuesView[FileScope]): class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes - def __init__(self, target: Union[str, CryticCompile], **kwargs): + def __init__(self, target: Union[str, CryticCompile], **kwargs) -> None: """ Args: target (str | CryticCompile) @@ -91,6 +91,7 @@ def __init__(self, target: Union[str, CryticCompile], **kwargs): self.codex_temperature = kwargs.get("codex_temperature", 0) self.codex_max_tokens = kwargs.get("codex_max_tokens", 300) self.codex_log = kwargs.get("codex_log", False) + self.codex_organization: Optional[str] = kwargs.get("codex_organization", None) self.no_fail = kwargs.get("no_fail", False) @@ -203,6 +204,16 @@ def register_detector(self, detector_class: Type[AbstractDetector]) -> None: + def unregister_detector(self, detector_class: Type[AbstractDetector]) -> None: + """ + :param detector_class: Class inheriting from `AbstractDetector`. + """ + + for obj in self._detectors: + if isinstance(obj, detector_class): + self._detectors.remove(obj) + return + def register_printer(self, printer_class: Type[AbstractPrinter]) -> None: """ :param printer_class: Class inheriting from `AbstractPrinter`. @@ -212,6 +223,16 @@ def register_printer(self, printer_class: Type[AbstractPrinter]) -> None: instance = printer_class(self, logger_printer) self._printers.append(instance) + def unregister_printer(self, printer_class: Type[AbstractPrinter]) -> None: + """ + :param printer_class: Class inheriting from `AbstractPrinter`. + """ + + for obj in self._printers: + if isinstance(obj, printer_class): + self._printers.remove(obj) + return + def run_detectors(self) -> List[Dict]: """ :return: List of registered detectors results. @@ -223,7 +244,7 @@ def run_detectors(self) -> List[Dict]: self.write_results_to_hide() return results - def run_printers(self): + def run_printers(self) -> List[Output]: """ :return: List of registered printers outputs. """ @@ -231,5 +252,5 @@ def run_printers(self): return [p.output(self._crytic_compile.target).data for p in self._printers] @property - def triage_mode(self): + def triage_mode(self) -> bool: return self._triage_mode diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py index 3a11738f31..444cec7d70 100644 --- a/slither/slithir/convert.py +++ b/slither/slithir/convert.py @@ -1,6 +1,6 @@ import logging from pathlib import Path -from typing import List, TYPE_CHECKING, Union, Optional +from typing import Any, List, TYPE_CHECKING, Union, Optional # pylint: disable= too-many-lines,import-outside-toplevel,too-many-branches,too-many-statements,too-many-nested-blocks from slither.core.declarations import ( @@ -34,7 +34,7 @@ MaxValues, ) from slither.core.solidity_types.type import Type -from slither.core.solidity_types.type_alias import TypeAlias +from slither.core.solidity_types.type_alias import TypeAliasTopLevel, TypeAlias from slither.core.variables.function_type_variable import FunctionTypeVariable from slither.core.variables.state_variable import StateVariable from slither.core.variables.variable import Variable @@ -83,15 +83,36 @@ from slither.utils.function import get_function_id from slither.utils.type import export_nested_types_from_variable from slither.visitors.slithir.expression_to_slithir import ExpressionToSlithIR +import slither.core.declarations.contract +import slither.core.declarations.function +import slither.core.solidity_types.elementary_type +import slither.core.solidity_types.function_type +import slither.core.solidity_types.user_defined_type +import slither.slithir.operations.assignment +import slither.slithir.operations.binary +import slither.slithir.operations.call +import slither.slithir.operations.high_level_call +import slither.slithir.operations.index +import slither.slithir.operations.init_array +import slither.slithir.operations.internal_call +import slither.slithir.operations.length +import slither.slithir.operations.library_call +import slither.slithir.operations.low_level_call +import slither.slithir.operations.member +import slither.slithir.operations.operation +import slither.slithir.operations.send +import slither.slithir.operations.solidity_call +import slither.slithir.operations.transfer +import slither.slithir.variables.temporary +from slither.core.expressions.expression import Expression if TYPE_CHECKING: from slither.core.cfg.node import Node - from slither.core.compilation_unit import SlitherCompilationUnit logger = logging.getLogger("ConvertToIR") -def convert_expression(expression, node): +def convert_expression(expression: Expression, node: "Node") -> List[Any]: # handle standlone expression # such as return true; from slither.core.cfg.node import NodeType @@ -143,7 +164,7 @@ def convert_expression(expression, node): ################################################################################### -def is_value(ins): +def is_value(ins: Operation) -> bool: if isinstance(ins, TmpCall): if isinstance(ins.ori, Member): if ins.ori.variable_right == "value": @@ -151,7 +172,7 @@ def is_value(ins): return False -def is_gas(ins): +def is_gas(ins: Operation) -> bool: if isinstance(ins, TmpCall): if isinstance(ins.ori, Member): if ins.ori.variable_right == "gas": @@ -159,7 +180,7 @@ def is_gas(ins): return False -def _fits_under_integer(val: int, can_be_int: bool, can_be_uint) -> List[str]: +def _fits_under_integer(val: int, can_be_int: bool, can_be_uint: bool) -> List[str]: """ Return the list of uint/int that can contain val @@ -271,7 +292,7 @@ def _find_function_from_parameter( return None -def is_temporary(ins): +def is_temporary(ins: Operation) -> bool: return isinstance( ins, (Argument, TmpNewElementaryType, TmpNewContract, TmpNewArray, TmpNewStructure), @@ -300,7 +321,7 @@ def _make_function_type(func: Function) -> FunctionType: ################################################################################### -def integrate_value_gas(result): +def integrate_value_gas(result: List[Operation]) -> List[Operation]: """ Integrate value and gas temporary arguments to call instruction """ @@ -504,7 +525,7 @@ def _convert_type_contract(ir: Member) -> Assignment: raise SlithIRError(f"type({contract.name}).{ir.variable_right} is unknown") -def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals +def propagate_types(ir: Operation, node: "Node"): # pylint: disable=too-many-locals # propagate the type node_function = node.function using_for = ( @@ -554,9 +575,9 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals if (isinstance(t, ElementaryType) and t.name == "address") or ( isinstance(t, TypeAlias) and t.underlying_type.name == "address" ): - # Cannot be a top level function with this. - assert isinstance(node_function, FunctionContract) if ir.destination.name == "this": + # Cannot be a top level function with this. + assert isinstance(node_function, FunctionContract) # the target contract is the contract itself return convert_type_of_high_and_internal_level_call( ir, node_function.contract @@ -824,7 +845,10 @@ def resolve_error(error_name : str, c : Contract) -> Optional["CustomError"]: return ce return None -def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]): # pylint: disable=too-many-locals +# pylint: disable=too-many-locals +def extract_tmp_call( + ins: TmpCall, contract: Optional[Contract] +) -> slither.slithir.operations.call.Call: assert isinstance(ins, TmpCall) if isinstance(ins.called, Variable) and isinstance(ins.called.type, FunctionType): # If the call is made to a variable member, where the member is this @@ -1130,7 +1154,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]): # pylint: dis ################################################################################### -def can_be_low_level(ir): +def can_be_low_level(ir: HighLevelCall) -> bool: return ir.function_name in [ "transfer", "send", @@ -1141,7 +1165,9 @@ def can_be_low_level(ir): ] -def convert_to_low_level(ir): +def convert_to_low_level( + ir: HighLevelCall, +) -> Union[Send, LowLevelCall, Transfer,]: """ Convert to a transfer/send/or low level call The funciton assume to receive a correct IR @@ -1181,7 +1207,7 @@ def convert_to_low_level(ir): raise SlithIRError(f"Incorrect conversion to low level {ir}") -def can_be_solidity_func(ir) -> bool: +def can_be_solidity_func(ir: HighLevelCall) -> bool: if not isinstance(ir, HighLevelCall): return False return ir.destination.name == "abi" and ir.function_name in [ @@ -1194,7 +1220,9 @@ def can_be_solidity_func(ir) -> bool: ] -def convert_to_solidity_func(ir): +def convert_to_solidity_func( + ir: HighLevelCall, +) -> SolidityCall: """ Must be called after can_be_solidity_func :param ir: @@ -1230,7 +1258,9 @@ def convert_to_solidity_func(ir): return new_ir -def convert_to_push_expand_arr(ir, node, ret): +def convert_to_push_expand_arr( + ir: HighLevelCall, node: "Node", ret: List[Any] +) -> TemporaryVariable: arr = ir.destination length = ReferenceVariable(node) @@ -1265,7 +1295,18 @@ def convert_to_push_expand_arr(ir, node, ret): return length_val -def convert_to_push_set_val(ir, node, length_val, ret): +def convert_to_push_set_val( + ir: HighLevelCall, + node: "Node", + length_val: TemporaryVariable, + ret: List[ + Union[ + Length, + Assignment, + Binary, + ] + ], +) -> None: arr = ir.destination new_type = ir.destination.type.type @@ -1300,7 +1341,17 @@ def convert_to_push_set_val(ir, node, length_val, ret): ret.append(ir_assign_value) -def convert_to_push(ir, node): +def convert_to_push( + ir: slither.slithir.operations.high_level_call.HighLevelCall, node: "Node" +) -> List[ + Union[ + slither.slithir.operations.length.Length, + slither.slithir.operations.assignment.Assignment, + slither.slithir.operations.binary.Binary, + slither.slithir.operations.index.Index, + slither.slithir.operations.init_array.InitArray, + ] +]: """ Convert a call to a series of operations to push a new value onto the array @@ -1374,7 +1425,17 @@ def convert_to_pop(ir, node): return ret -def look_for_library_or_top_level(contract, ir, using_for, t): +def look_for_library_or_top_level( + contract: Contract, + ir: HighLevelCall, + using_for, + t: Union[ + UserDefinedType, + ElementaryType, + str, + TypeAliasTopLevel, + ], +) -> Optional[Union[LibraryCall, InternalCall,]]: for destination in using_for[t]: if isinstance(destination, FunctionTopLevel) and destination.name == ir.function_name: arguments = [ir.destination] + ir.arguments @@ -1419,7 +1480,9 @@ def look_for_library_or_top_level(contract, ir, using_for, t): return None -def convert_to_library_or_top_level(ir, node, using_for): +def convert_to_library_or_top_level( + ir: HighLevelCall, node: "Node", using_for +) -> Optional[Union[LibraryCall, InternalCall,]]: # We use contract_declarer, because Solidity resolve the library # before resolving the inheritance. # Though we could use .contract as libraries cannot be shadowed @@ -1438,7 +1501,12 @@ def convert_to_library_or_top_level(ir, node, using_for): return None -def get_type(t): +def get_type( + t: Union[ + UserDefinedType, + ElementaryType, + ] +) -> str: """ Convert a type to a str If the instance is a Contract, return 'address' instead @@ -1457,7 +1525,7 @@ def _can_be_implicitly_converted(source: str, target: str) -> bool: return source == target -def convert_type_library_call(ir: HighLevelCall, lib_contract: Contract): +def convert_type_library_call(ir: HighLevelCall, lib_contract: Contract) -> Optional[LibraryCall]: func = None candidates = [ f @@ -1668,7 +1736,7 @@ def convert_type_of_high_and_internal_level_call( ################################################################################### -def find_references_origin(irs): +def find_references_origin(irs: List[Operation]) -> None: """ Make lvalue of each Index, Member operation points to the left variable @@ -1705,7 +1773,7 @@ def remove_temporary(result): return result -def remove_unused(result): +def remove_unused(result: List[Operation]) -> List[Operation]: removed = True if not result: @@ -1752,7 +1820,7 @@ def remove_unused(result): ################################################################################### -def convert_constant_types(irs): +def convert_constant_types(irs: List[Operation]) -> None: """ late conversion of uint -> type for constant (Literal) :param irs: @@ -1828,7 +1896,7 @@ def convert_constant_types(irs): ################################################################################### -def convert_delete(irs): +def convert_delete(irs: List[Operation]) -> None: """ Convert the lvalue of the Delete to point to the variable removed This can only be done after find_references_origin is called @@ -1849,7 +1917,7 @@ def convert_delete(irs): ################################################################################### -def _find_source_mapping_references(irs: List[Operation]): +def _find_source_mapping_references(irs: List[Operation]) -> None: for ir in irs: if isinstance(ir, NewContract): @@ -1864,7 +1932,7 @@ def _find_source_mapping_references(irs: List[Operation]): ################################################################################### -def apply_ir_heuristics(irs: List[Operation], node: "Node"): +def apply_ir_heuristics(irs: List[Operation], node: "Node") -> List[Operation]: """ Apply a set of heuristic to improve slithIR """ diff --git a/slither/slithir/operations/assignment.py b/slither/slithir/operations/assignment.py index 3d05c3040b..0ed5f70a49 100644 --- a/slither/slithir/operations/assignment.py +++ b/slither/slithir/operations/assignment.py @@ -1,15 +1,21 @@ import logging +from typing import List from slither.core.declarations.function import Function from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue from slither.slithir.variables import TupleVariable, ReferenceVariable +from slither.core.source_mapping.source_mapping import SourceMapping +from slither.core.variables.variable import Variable + logger = logging.getLogger("AssignmentOperationIR") class Assignment(OperationWithLValue): - def __init__(self, left_variable, right_variable, variable_return_type): + def __init__( + self, left_variable: Variable, right_variable: SourceMapping, variable_return_type + ) -> None: assert is_valid_lvalue(left_variable) assert is_valid_rvalue(right_variable) or isinstance( right_variable, (Function, TupleVariable) @@ -25,7 +31,7 @@ def variables(self): return list(self._variables) @property - def read(self): + def read(self) -> List[SourceMapping]: return [self.rvalue] @property @@ -33,7 +39,7 @@ def variable_return_type(self): return self._variable_return_type @property - def rvalue(self): + def rvalue(self) -> SourceMapping: return self._rvalue def __str__(self): diff --git a/slither/slithir/operations/binary.py b/slither/slithir/operations/binary.py index d8844fa329..ad65e3e758 100644 --- a/slither/slithir/operations/binary.py +++ b/slither/slithir/operations/binary.py @@ -1,4 +1,6 @@ import logging +from typing import List + from enum import Enum from slither.core.declarations import Function @@ -7,33 +9,36 @@ from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue from slither.slithir.variables import ReferenceVariable +from slither.core.source_mapping.source_mapping import SourceMapping +from slither.core.variables.variable import Variable + logger = logging.getLogger("BinaryOperationIR") class BinaryType(Enum): - POWER = 0 # ** - MULTIPLICATION = 1 # * - DIVISION = 2 # / - MODULO = 3 # % - ADDITION = 4 # + - SUBTRACTION = 5 # - - LEFT_SHIFT = 6 # << - RIGHT_SHIFT = 7 # >> - AND = 8 # & - CARET = 9 # ^ - OR = 10 # | - LESS = 11 # < - GREATER = 12 # > - LESS_EQUAL = 13 # <= - GREATER_EQUAL = 14 # >= - EQUAL = 15 # == - NOT_EQUAL = 16 # != - ANDAND = 17 # && - OROR = 18 # || + POWER = "**" + MULTIPLICATION = "*" + DIVISION = "/" + MODULO = "%" + ADDITION = "+" + SUBTRACTION = "-" + LEFT_SHIFT = "<<" + RIGHT_SHIFT = ">>" + AND = "&" + CARET = "^" + OR = "|" + LESS = "<" + GREATER = ">" + LESS_EQUAL = "<=" + GREATER_EQUAL = ">=" + EQUAL = "==" + NOT_EQUAL = "!=" + ANDAND = "&&" + OROR = "||" @staticmethod - def return_bool(operation_type): + def return_bool(operation_type: "BinaryType") -> bool: return operation_type in [ BinaryType.OROR, BinaryType.ANDAND, @@ -98,50 +103,15 @@ def can_be_checked_for_overflow(self): BinaryType.DIVISION, ] - def __str__(self): # pylint: disable=too-many-branches - if self == BinaryType.POWER: - return "**" - if self == BinaryType.MULTIPLICATION: - return "*" - if self == BinaryType.DIVISION: - return "/" - if self == BinaryType.MODULO: - return "%" - if self == BinaryType.ADDITION: - return "+" - if self == BinaryType.SUBTRACTION: - return "-" - if self == BinaryType.LEFT_SHIFT: - return "<<" - if self == BinaryType.RIGHT_SHIFT: - return ">>" - if self == BinaryType.AND: - return "&" - if self == BinaryType.CARET: - return "^" - if self == BinaryType.OR: - return "|" - if self == BinaryType.LESS: - return "<" - if self == BinaryType.GREATER: - return ">" - if self == BinaryType.LESS_EQUAL: - return "<=" - if self == BinaryType.GREATER_EQUAL: - return ">=" - if self == BinaryType.EQUAL: - return "==" - if self == BinaryType.NOT_EQUAL: - return "!=" - if self == BinaryType.ANDAND: - return "&&" - if self == BinaryType.OROR: - return "||" - raise SlithIRError(f"str: Unknown operation type {self} {type(self)})") - class Binary(OperationWithLValue): - def __init__(self, result, left_variable, right_variable, operation_type: BinaryType): + def __init__( + self, + result: Variable, + left_variable: SourceMapping, + right_variable: Variable, + operation_type: BinaryType, + ) -> None: assert is_valid_rvalue(left_variable) or isinstance(left_variable, Function) assert is_valid_rvalue(right_variable) or isinstance(right_variable, Function) assert is_valid_lvalue(result) @@ -156,7 +126,7 @@ def __init__(self, result, left_variable, right_variable, operation_type: Binary result.set_type(left_variable.type) @property - def read(self): + def read(self) -> List[SourceMapping]: return [self.variable_left, self.variable_right] @property @@ -164,22 +134,22 @@ def get_variable(self): return self._variables @property - def variable_left(self): + def variable_left(self) -> SourceMapping: return self._variables[0] @property - def variable_right(self): + def variable_right(self) -> Variable: return self._variables[1] @property - def type(self): + def type(self) -> BinaryType: return self._type @property def type_str(self): if self.node.scope.is_checked and self._type.can_be_checked_for_overflow(): - return "(c)" + str(self._type) - return str(self._type) + return "(c)" + self._type.value + return self._type.value def __str__(self): if isinstance(self.lvalue, ReferenceVariable): diff --git a/slither/slithir/operations/codesize.py b/slither/slithir/operations/codesize.py index e7a9108064..6640f4fd89 100644 --- a/slither/slithir/operations/codesize.py +++ b/slither/slithir/operations/codesize.py @@ -1,10 +1,19 @@ +from typing import List, Union from slither.core.solidity_types import ElementaryType from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue +from slither.core.variables.local_variable import LocalVariable +from slither.slithir.variables.local_variable import LocalIRVariable +from slither.slithir.variables.reference import ReferenceVariable +from slither.slithir.variables.reference_ssa import ReferenceVariableSSA class CodeSize(OperationWithLValue): - def __init__(self, value, lvalue): + def __init__( + self, + value: Union[LocalVariable, LocalIRVariable], + lvalue: Union[ReferenceVariableSSA, ReferenceVariable], + ) -> None: super().__init__() assert is_valid_rvalue(value) assert is_valid_lvalue(lvalue) @@ -13,11 +22,11 @@ def __init__(self, value, lvalue): lvalue.set_type(ElementaryType("uint256")) @property - def read(self): + def read(self) -> List[Union[LocalIRVariable, LocalVariable]]: return [self._value] @property - def value(self): + def value(self) -> LocalVariable: return self._value def __str__(self): diff --git a/slither/slithir/operations/condition.py b/slither/slithir/operations/condition.py index 5ba959a73f..41fb3d933d 100644 --- a/slither/slithir/operations/condition.py +++ b/slither/slithir/operations/condition.py @@ -1,6 +1,13 @@ +from typing import List, Union from slither.slithir.operations.operation import Operation from slither.slithir.utils.utils import is_valid_rvalue +from slither.core.variables.local_variable import LocalVariable +from slither.slithir.variables.constant import Constant +from slither.slithir.variables.local_variable import LocalIRVariable +from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA +from slither.core.variables.variable import Variable class Condition(Operation): @@ -9,17 +16,26 @@ class Condition(Operation): Only present as last operation in conditional node """ - def __init__(self, value): + def __init__( + self, + value: Union[ + LocalVariable, TemporaryVariableSSA, TemporaryVariable, Constant, LocalIRVariable + ], + ) -> None: assert is_valid_rvalue(value) super().__init__() self._value = value @property - def read(self): + def read( + self, + ) -> List[ + Union[LocalIRVariable, Constant, LocalVariable, TemporaryVariableSSA, TemporaryVariable] + ]: return [self.value] @property - def value(self): + def value(self) -> Variable: return self._value def __str__(self): diff --git a/slither/slithir/operations/delete.py b/slither/slithir/operations/delete.py index 4fb05b8f51..496d170ad6 100644 --- a/slither/slithir/operations/delete.py +++ b/slither/slithir/operations/delete.py @@ -1,6 +1,11 @@ +from typing import List, Union from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.utils.utils import is_valid_lvalue +from slither.core.variables.state_variable import StateVariable +from slither.slithir.variables.reference import ReferenceVariable +from slither.slithir.variables.reference_ssa import ReferenceVariableSSA +from slither.slithir.variables.state_variable import StateIRVariable class Delete(OperationWithLValue): @@ -9,18 +14,26 @@ class Delete(OperationWithLValue): of its operand """ - def __init__(self, lvalue, variable): + def __init__( + self, + lvalue: Union[StateIRVariable, StateVariable, ReferenceVariable], + variable: Union[StateIRVariable, StateVariable, ReferenceVariable, ReferenceVariableSSA], + ) -> None: assert is_valid_lvalue(variable) super().__init__() self._variable = variable self._lvalue = lvalue @property - def read(self): + def read( + self, + ) -> List[Union[StateIRVariable, ReferenceVariable, ReferenceVariableSSA, StateVariable]]: return [self.variable] @property - def variable(self): + def variable( + self, + ) -> Union[StateIRVariable, StateVariable, ReferenceVariable, ReferenceVariableSSA]: return self._variable def __str__(self): diff --git a/slither/slithir/operations/event_call.py b/slither/slithir/operations/event_call.py index 20ff56305d..18bcc98c08 100644 --- a/slither/slithir/operations/event_call.py +++ b/slither/slithir/operations/event_call.py @@ -1,8 +1,10 @@ +from typing import Any, List, Union from slither.slithir.operations.call import Call +from slither.slithir.variables.constant import Constant class EventCall(Call): - def __init__(self, destination): + def __init__(self, destination) -> None: super().__init__() self._destination = destination @@ -11,11 +13,11 @@ def destination(self): return self._destination @property - def name(self): - return self.destination.name + def name(self) -> Union[str, Constant]: + return self._destination.name @property - def read(self): + def read(self) -> List[Any]: return self._unroll(self.arguments) def __str__(self): diff --git a/slither/slithir/operations/high_level_call.py b/slither/slithir/operations/high_level_call.py index ff72a0899d..a129175193 100644 --- a/slither/slithir/operations/high_level_call.py +++ b/slither/slithir/operations/high_level_call.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import List, Optional, Union from slither.slithir.operations.call import Call from slither.slithir.operations.lvalue import OperationWithLValue @@ -8,6 +8,10 @@ from slither.slithir.utils.utils import is_valid_lvalue from slither.slithir.variables.constant import Constant +from slither.core.source_mapping.source_mapping import SourceMapping +from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA +from slither.slithir.variables.tuple import TupleVariable class HighLevelCall(Call, OperationWithLValue): @@ -16,7 +20,14 @@ class HighLevelCall(Call, OperationWithLValue): """ # pylint: disable=too-many-arguments,too-many-instance-attributes - def __init__(self, destination, function_name, nbr_arguments, result, type_call): + def __init__( + self, + destination: SourceMapping, + function_name: Constant, + nbr_arguments: int, + result: Optional[Union[TemporaryVariable, TupleVariable, TemporaryVariableSSA]], + type_call: str, + ) -> None: assert isinstance(function_name, Constant) assert is_valid_lvalue(result) or result is None self._check_destination(destination) @@ -34,7 +45,7 @@ def __init__(self, destination, function_name, nbr_arguments, result, type_call) # Development function, to be removed once the code is stable # It is ovveride by LbraryCall - def _check_destination(self, destination): # pylint: disable=no-self-use + def _check_destination(self, destination: SourceMapping) -> None: # pylint: disable=no-self-use assert isinstance(destination, (Variable, SolidityVariable)) @property @@ -62,17 +73,17 @@ def call_gas(self, v): self._call_gas = v @property - def read(self): + def read(self) -> List[SourceMapping]: all_read = [self.destination, self.call_gas, self.call_value] + self._unroll(self.arguments) # remove None - return [x for x in all_read if x] + [self.destination] + return [x for x in all_read if x] @property - def destination(self): + def destination(self) -> SourceMapping: return self._destination @property - def function_name(self): + def function_name(self) -> Constant: return self._function_name @property @@ -84,11 +95,11 @@ def function(self, function): self._function_instance = function @property - def nbr_arguments(self): + def nbr_arguments(self) -> int: return self._nbr_arguments @property - def type_call(self): + def type_call(self) -> str: return self._type_call ################################################################################### @@ -96,7 +107,7 @@ def type_call(self): # region Analyses ################################################################################### ################################################################################### - def is_static_call(self): + def is_static_call(self) -> bool: # If solidity >0.5, STATICCALL is used if self.compilation_unit.solc_version and self.compilation_unit.solc_version >= "0.5.0": if isinstance(self.function, Function) and (self.function.view or self.function.pure): @@ -105,7 +116,7 @@ def is_static_call(self): return True return False - def can_reenter(self, callstack=None): + def can_reenter(self, callstack: None = None) -> bool: """ Must be called after slithIR analysis pass For Solidity > 0.5, filter access to public variables and constant/pure/view @@ -134,7 +145,7 @@ def can_reenter(self, callstack=None): return True - def can_send_eth(self): + def can_send_eth(self) -> bool: """ Must be called after slithIR analysis pass :return: bool diff --git a/slither/slithir/operations/index.py b/slither/slithir/operations/index.py index 096cc7268a..ade84fe1d5 100644 --- a/slither/slithir/operations/index.py +++ b/slither/slithir/operations/index.py @@ -1,11 +1,22 @@ +from typing import List, Union from slither.core.declarations import SolidityVariableComposed from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue from slither.slithir.variables.reference import ReferenceVariable +from slither.core.solidity_types.elementary_type import ElementaryType +from slither.core.source_mapping.source_mapping import SourceMapping +from slither.core.variables.variable import Variable +from slither.slithir.variables.reference_ssa import ReferenceVariableSSA class Index(OperationWithLValue): - def __init__(self, result, left_variable, right_variable, index_type): + def __init__( + self, + result: Union[ReferenceVariable, ReferenceVariableSSA], + left_variable: Variable, + right_variable: SourceMapping, + index_type: Union[ElementaryType, str], + ) -> None: super().__init__() assert is_valid_lvalue(left_variable) or left_variable == SolidityVariableComposed( "msg.data" @@ -17,23 +28,23 @@ def __init__(self, result, left_variable, right_variable, index_type): self._lvalue = result @property - def read(self): + def read(self) -> List[SourceMapping]: return list(self.variables) @property - def variables(self): + def variables(self) -> List[SourceMapping]: return self._variables @property - def variable_left(self): + def variable_left(self) -> Variable: return self._variables[0] @property - def variable_right(self): + def variable_right(self) -> SourceMapping: return self._variables[1] @property - def index_type(self): + def index_type(self) -> Union[ElementaryType, str]: return self._type def __str__(self): diff --git a/slither/slithir/operations/init_array.py b/slither/slithir/operations/init_array.py index 23a95ebe7b..4f6b2f9faf 100644 --- a/slither/slithir/operations/init_array.py +++ b/slither/slithir/operations/init_array.py @@ -1,9 +1,15 @@ +from typing import List, Union from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.utils.utils import is_valid_rvalue +from slither.slithir.variables.constant import Constant +from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA class InitArray(OperationWithLValue): - def __init__(self, init_values, lvalue): + def __init__( + self, init_values: List[Constant], lvalue: Union[TemporaryVariableSSA, TemporaryVariable] + ) -> None: # init_values can be an array of n dimension # reduce was removed in py3 super().__init__() @@ -24,11 +30,11 @@ def check(elem): self._lvalue = lvalue @property - def read(self): + def read(self) -> List[Constant]: return self._unroll(self.init_values) @property - def init_values(self): + def init_values(self) -> List[Constant]: return list(self._init_values) def __str__(self): diff --git a/slither/slithir/operations/internal_call.py b/slither/slithir/operations/internal_call.py index c096bdceb2..395c688466 100644 --- a/slither/slithir/operations/internal_call.py +++ b/slither/slithir/operations/internal_call.py @@ -1,15 +1,26 @@ -from typing import Union, Tuple, List, Optional +from typing import Any, Union, Tuple, List, Optional from slither.core.declarations import Modifier from slither.core.declarations.function import Function from slither.core.declarations.function_contract import FunctionContract from slither.slithir.operations.call import Call from slither.slithir.operations.lvalue import OperationWithLValue +from slither.slithir.variables.constant import Constant +from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA +from slither.slithir.variables.tuple import TupleVariable +from slither.slithir.variables.tuple_ssa import TupleVariableSSA class InternalCall(Call, OperationWithLValue): # pylint: disable=too-many-instance-attributes def __init__( - self, function: Union[Function, Tuple[str, str]], nbr_arguments, result, type_call - ): + self, + function: Union[Function, Tuple[str, str]], + nbr_arguments: int, + result: Optional[ + Union[TupleVariableSSA, TemporaryVariableSSA, TupleVariable, TemporaryVariable] + ], + type_call: str, + ) -> None: super().__init__() self._contract_name = "" if isinstance(function, Function): @@ -30,7 +41,7 @@ def __init__( self.function_candidates: Optional[List[Function]] = None @property - def read(self): + def read(self) -> List[Any]: return list(self._unroll(self.arguments)) @property @@ -42,19 +53,19 @@ def function(self, f): self._function = f @property - def function_name(self): + def function_name(self) -> Constant: return self._function_name @property - def contract_name(self): + def contract_name(self) -> str: return self._contract_name @property - def nbr_arguments(self): + def nbr_arguments(self) -> int: return self._nbr_arguments @property - def type_call(self): + def type_call(self) -> str: return self._type_call @property diff --git a/slither/slithir/operations/internal_dynamic_call.py b/slither/slithir/operations/internal_dynamic_call.py index 39a8ae6a13..a1ad1aa15f 100644 --- a/slither/slithir/operations/internal_dynamic_call.py +++ b/slither/slithir/operations/internal_dynamic_call.py @@ -1,14 +1,25 @@ +from typing import List, Optional, Union from slither.core.solidity_types import FunctionType from slither.core.variables.variable import Variable from slither.slithir.operations.call import Call from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.utils.utils import is_valid_lvalue +from slither.core.variables.local_variable import LocalVariable +from slither.slithir.variables.constant import Constant +from slither.slithir.variables.local_variable import LocalIRVariable +from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA class InternalDynamicCall( Call, OperationWithLValue ): # pylint: disable=too-many-instance-attributes - def __init__(self, lvalue, function, function_type): + def __init__( + self, + lvalue: Optional[Union[TemporaryVariableSSA, TemporaryVariable]], + function: Union[LocalVariable, LocalIRVariable], + function_type: FunctionType, + ) -> None: assert isinstance(function_type, FunctionType) assert isinstance(function, Variable) assert is_valid_lvalue(lvalue) or lvalue is None @@ -22,15 +33,15 @@ def __init__(self, lvalue, function, function_type): self._call_gas = None @property - def read(self): + def read(self) -> List[Union[Constant, LocalIRVariable, LocalVariable]]: return self._unroll(self.arguments) + [self.function] @property - def function(self): + def function(self) -> Union[LocalVariable, LocalIRVariable]: return self._function @property - def function_type(self): + def function_type(self) -> FunctionType: return self._function_type @property diff --git a/slither/slithir/operations/length.py b/slither/slithir/operations/length.py index 9ba33e655d..46637dcc82 100644 --- a/slither/slithir/operations/length.py +++ b/slither/slithir/operations/length.py @@ -1,10 +1,21 @@ +from typing import List, Union from slither.core.solidity_types import ElementaryType from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue +from slither.core.variables.local_variable import LocalVariable +from slither.core.variables.state_variable import StateVariable +from slither.slithir.variables.local_variable import LocalIRVariable +from slither.slithir.variables.reference import ReferenceVariable +from slither.slithir.variables.reference_ssa import ReferenceVariableSSA +from slither.slithir.variables.state_variable import StateIRVariable class Length(OperationWithLValue): - def __init__(self, value, lvalue): + def __init__( + self, + value: Union[StateVariable, LocalIRVariable, LocalVariable, StateIRVariable], + lvalue: Union[ReferenceVariable, ReferenceVariableSSA], + ) -> None: super().__init__() assert is_valid_rvalue(value) assert is_valid_lvalue(lvalue) @@ -13,11 +24,11 @@ def __init__(self, value, lvalue): lvalue.set_type(ElementaryType("uint256")) @property - def read(self): + def read(self) -> List[Union[LocalVariable, StateVariable, LocalIRVariable, StateIRVariable]]: return [self._value] @property - def value(self): + def value(self) -> Union[StateVariable, LocalVariable]: return self._value def __str__(self): diff --git a/slither/slithir/operations/library_call.py b/slither/slithir/operations/library_call.py index 1f1f507a6a..ebe9bf5efd 100644 --- a/slither/slithir/operations/library_call.py +++ b/slither/slithir/operations/library_call.py @@ -9,10 +9,10 @@ class LibraryCall(HighLevelCall): """ # Development function, to be removed once the code is stable - def _check_destination(self, destination): + def _check_destination(self, destination: Contract) -> None: assert isinstance(destination, Contract) - def can_reenter(self, callstack=None): + def can_reenter(self, callstack: None = None) -> bool: """ Must be called after slithIR analysis pass :return: bool diff --git a/slither/slithir/operations/low_level_call.py b/slither/slithir/operations/low_level_call.py index 83bbbb336a..7e8c278b8a 100644 --- a/slither/slithir/operations/low_level_call.py +++ b/slither/slithir/operations/low_level_call.py @@ -1,9 +1,16 @@ +from typing import List, Union from slither.slithir.operations.call import Call from slither.slithir.operations.lvalue import OperationWithLValue from slither.core.variables.variable import Variable from slither.core.declarations.solidity_variables import SolidityVariable from slither.slithir.variables.constant import Constant +from slither.core.variables.local_variable import LocalVariable +from slither.slithir.variables.local_variable import LocalIRVariable +from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA +from slither.slithir.variables.tuple import TupleVariable +from slither.slithir.variables.tuple_ssa import TupleVariableSSA class LowLevelCall(Call, OperationWithLValue): # pylint: disable=too-many-instance-attributes @@ -11,7 +18,14 @@ class LowLevelCall(Call, OperationWithLValue): # pylint: disable=too-many-insta High level message call """ - def __init__(self, destination, function_name, nbr_arguments, result, type_call): + def __init__( + self, + destination: Union[LocalVariable, LocalIRVariable, TemporaryVariableSSA, TemporaryVariable], + function_name: Constant, + nbr_arguments: int, + result: Union[TupleVariable, TupleVariableSSA], + type_call: str, + ) -> None: # pylint: disable=too-many-arguments assert isinstance(destination, (Variable, SolidityVariable)) assert isinstance(function_name, Constant) @@ -51,12 +65,16 @@ def call_gas(self, v): self._call_gas = v @property - def read(self): + def read( + self, + ) -> List[ + Union[LocalIRVariable, Constant, LocalVariable, TemporaryVariableSSA, TemporaryVariable] + ]: all_read = [self.destination, self.call_gas, self.call_value] + self.arguments # remove None return self._unroll([x for x in all_read if x]) - def can_reenter(self, _callstack=None): + def can_reenter(self, _callstack: None = None) -> bool: """ Must be called after slithIR analysis pass :return: bool @@ -65,7 +83,7 @@ def can_reenter(self, _callstack=None): return False return True - def can_send_eth(self): + def can_send_eth(self) -> bool: """ Must be called after slithIR analysis pass :return: bool @@ -73,19 +91,21 @@ def can_send_eth(self): return self._call_value is not None @property - def destination(self): + def destination( + self, + ) -> Union[LocalVariable, LocalIRVariable, TemporaryVariableSSA, TemporaryVariable]: return self._destination @property - def function_name(self): + def function_name(self) -> Constant: return self._function_name @property - def nbr_arguments(self): + def nbr_arguments(self) -> int: return self._nbr_arguments @property - def type_call(self): + def type_call(self) -> str: return self._type_call def __str__(self): diff --git a/slither/slithir/operations/lvalue.py b/slither/slithir/operations/lvalue.py index 4571b9f1f8..d9b800c92b 100644 --- a/slither/slithir/operations/lvalue.py +++ b/slither/slithir/operations/lvalue.py @@ -1,3 +1,4 @@ +from typing import Any, List from slither.slithir.operations.operation import Operation @@ -6,7 +7,7 @@ class OperationWithLValue(Operation): Operation with a lvalue """ - def __init__(self): + def __init__(self) -> None: super().__init__() self._lvalue = None @@ -16,7 +17,7 @@ def lvalue(self): return self._lvalue @property - def used(self): + def used(self) -> List[Any]: return self.read + [self.lvalue] @lvalue.setter diff --git a/slither/slithir/operations/member.py b/slither/slithir/operations/member.py index f0c6ae5231..9a561ea87a 100644 --- a/slither/slithir/operations/member.py +++ b/slither/slithir/operations/member.py @@ -1,3 +1,4 @@ +from typing import List, Union from slither.core.declarations import Contract, Function from slither.core.declarations.custom_error import CustomError from slither.core.declarations.enum import Enum @@ -7,10 +8,17 @@ from slither.slithir.utils.utils import is_valid_rvalue from slither.slithir.variables.constant import Constant from slither.slithir.variables.reference import ReferenceVariable +from slither.core.source_mapping.source_mapping import SourceMapping +from slither.slithir.variables.reference_ssa import ReferenceVariableSSA class Member(OperationWithLValue): - def __init__(self, variable_left, variable_right, result): + def __init__( + self, + variable_left: SourceMapping, + variable_right: Constant, + result: Union[ReferenceVariable, ReferenceVariableSSA], + ) -> None: # Function can happen for something like # library FunctionExtensions { # function h(function() internal _t, uint8) internal { } @@ -38,15 +46,15 @@ def __init__(self, variable_left, variable_right, result): self._value = None @property - def read(self): + def read(self) -> List[SourceMapping]: return [self.variable_left, self.variable_right] @property - def variable_left(self): + def variable_left(self) -> SourceMapping: return self._variable_left @property - def variable_right(self): + def variable_right(self) -> Constant: return self._variable_right @property diff --git a/slither/slithir/operations/new_array.py b/slither/slithir/operations/new_array.py index 57ee6dcf03..8dad8532f7 100644 --- a/slither/slithir/operations/new_array.py +++ b/slither/slithir/operations/new_array.py @@ -1,10 +1,22 @@ +from typing import List, Union, TYPE_CHECKING from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.operations.call import Call from slither.core.solidity_types.type import Type +if TYPE_CHECKING: + from slither.core.solidity_types.type_alias import TypeAliasTopLevel + from slither.slithir.variables.constant import Constant + from slither.slithir.variables.temporary import TemporaryVariable + from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA + class NewArray(Call, OperationWithLValue): - def __init__(self, depth, array_type, lvalue): + def __init__( + self, + depth: int, + array_type: "TypeAliasTopLevel", + lvalue: Union["TemporaryVariableSSA", "TemporaryVariable"], + ) -> None: super().__init__() assert isinstance(array_type, Type) self._depth = depth @@ -13,15 +25,15 @@ def __init__(self, depth, array_type, lvalue): self._lvalue = lvalue @property - def array_type(self): + def array_type(self) -> "TypeAliasTopLevel": return self._array_type @property - def read(self): + def read(self) -> List["Constant"]: return self._unroll(self.arguments) @property - def depth(self): + def depth(self) -> int: return self._depth def __str__(self): diff --git a/slither/slithir/operations/new_contract.py b/slither/slithir/operations/new_contract.py index fb0014c769..08ddbd9604 100644 --- a/slither/slithir/operations/new_contract.py +++ b/slither/slithir/operations/new_contract.py @@ -1,10 +1,17 @@ +from typing import Optional, Any, List, Union from slither.slithir.operations import Call, OperationWithLValue from slither.slithir.utils.utils import is_valid_lvalue from slither.slithir.variables.constant import Constant +from slither.core.declarations.contract import Contract +from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA +from slither.core.declarations.function_contract import FunctionContract class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instance-attributes - def __init__(self, contract_name, lvalue): + def __init__( + self, contract_name: Constant, lvalue: Union[TemporaryVariableSSA, TemporaryVariable] + ) -> None: assert isinstance(contract_name, Constant) assert is_valid_lvalue(lvalue) super().__init__() @@ -40,15 +47,17 @@ def call_salt(self, s): self._call_salt = s @property - def contract_name(self): + def contract_name(self) -> Constant: return self._contract_name @property - def read(self): - return self._unroll(self.arguments) + def read(self) -> List[Any]: + all_read = [self.call_salt, self.call_value] + self._unroll(self.arguments) + # remove None + return [x for x in all_read if x] @property - def contract_created(self): + def contract_created(self) -> Contract: contract_name = self.contract_name contract_instance = self.node.file_scope.get_contract_from_name(contract_name) return contract_instance @@ -59,7 +68,7 @@ def contract_created(self): ################################################################################### ################################################################################### - def can_reenter(self, callstack=None): + def can_reenter(self, callstack: Optional[List[FunctionContract]] = None) -> bool: """ Must be called after slithIR analysis pass For Solidity > 0.5, filter access to public variables and constant/pure/view @@ -76,7 +85,7 @@ def can_reenter(self, callstack=None): callstack = callstack + [constructor] return constructor.can_reenter(callstack) - def can_send_eth(self): + def can_send_eth(self) -> bool: """ Must be called after slithIR analysis pass :return: bool diff --git a/slither/slithir/operations/new_structure.py b/slither/slithir/operations/new_structure.py index 16a8af785f..752de6a3d8 100644 --- a/slither/slithir/operations/new_structure.py +++ b/slither/slithir/operations/new_structure.py @@ -1,13 +1,21 @@ +from typing import List, Union + from slither.slithir.operations.call import Call from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.utils.utils import is_valid_lvalue from slither.core.declarations.structure import Structure +from slither.core.declarations.structure_contract import StructureContract +from slither.slithir.variables.constant import Constant +from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA class NewStructure(Call, OperationWithLValue): - def __init__(self, structure, lvalue): + def __init__( + self, structure: StructureContract, lvalue: Union[TemporaryVariableSSA, TemporaryVariable] + ) -> None: super().__init__() assert isinstance(structure, Structure) assert is_valid_lvalue(lvalue) @@ -16,11 +24,11 @@ def __init__(self, structure, lvalue): self._lvalue = lvalue @property - def read(self): + def read(self) -> List[Union[TemporaryVariableSSA, TemporaryVariable, Constant]]: return self._unroll(self.arguments) @property - def structure(self): + def structure(self) -> StructureContract: return self._structure @property diff --git a/slither/slithir/operations/nop.py b/slither/slithir/operations/nop.py index 2d2d360dd7..387ca3cad8 100644 --- a/slither/slithir/operations/nop.py +++ b/slither/slithir/operations/nop.py @@ -1,9 +1,13 @@ -from .operation import Operation +from typing import List + + +from slither.core.variables.variable import Variable +from slither.slithir.operations import Operation class Nop(Operation): @property - def read(self): + def read(self) -> List[Variable]: return [] @property diff --git a/slither/slithir/operations/operation.py b/slither/slithir/operations/operation.py index fa1db89c24..fcf5f48686 100644 --- a/slither/slithir/operations/operation.py +++ b/slither/slithir/operations/operation.py @@ -1,7 +1,9 @@ import abc +from typing import Any, List from slither.core.context.context import Context from slither.core.children.child_expression import ChildExpression from slither.core.children.child_node import ChildNode +from slither.core.variables.variable import Variable from slither.utils.utils import unroll @@ -25,7 +27,7 @@ def used(self): class Operation(Context, ChildExpression, ChildNode, AbstractOperation): @property - def used(self): + def used(self) -> List[Variable]: """ By default used is all the variables read """ @@ -33,5 +35,5 @@ def used(self): # if array inside the parameters @staticmethod - def _unroll(l): + def _unroll(l: List[Any]) -> List[Any]: return unroll(l) diff --git a/slither/slithir/operations/phi.py b/slither/slithir/operations/phi.py index a4fa0217e6..dd416e8f23 100644 --- a/slither/slithir/operations/phi.py +++ b/slither/slithir/operations/phi.py @@ -1,9 +1,19 @@ +from typing import List, Set, Union, TYPE_CHECKING from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.utils.utils import is_valid_lvalue +from slither.core.declarations.solidity_variables import SolidityVariableComposed +from slither.slithir.variables.local_variable import LocalIRVariable +from slither.slithir.variables.state_variable import StateIRVariable +from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA + +if TYPE_CHECKING: + from slither.core.cfg.node import Node class Phi(OperationWithLValue): - def __init__(self, left_variable, nodes): + def __init__( + self, left_variable: Union[LocalIRVariable, StateIRVariable], nodes: Set["Node"] + ) -> None: # When Phi operations are created the # correct indexes of the variables are not yet computed # We store the nodes where the variables are written @@ -17,7 +27,11 @@ def __init__(self, left_variable, nodes): self._nodes = nodes @property - def read(self): + def read( + self, + ) -> List[ + Union[SolidityVariableComposed, LocalIRVariable, TemporaryVariableSSA, StateIRVariable] + ]: return self.rvalues @property @@ -29,7 +43,7 @@ def rvalues(self, vals): self._rvalues = vals @property - def nodes(self): + def nodes(self) -> Set["Node"]: return self._nodes def __str__(self): diff --git a/slither/slithir/operations/phi_callback.py b/slither/slithir/operations/phi_callback.py index 486015b0c9..0c8994056c 100644 --- a/slither/slithir/operations/phi_callback.py +++ b/slither/slithir/operations/phi_callback.py @@ -1,9 +1,24 @@ +from typing import List, Set, Union, TYPE_CHECKING + from slither.slithir.utils.utils import is_valid_lvalue from slither.slithir.operations.phi import Phi +from slither.slithir.operations.high_level_call import HighLevelCall +from slither.slithir.operations.internal_call import InternalCall +from slither.slithir.variables.state_variable import StateIRVariable + +if TYPE_CHECKING: + from slither.core.cfg.node import Node + class PhiCallback(Phi): - def __init__(self, left_variable, nodes, call_ir, rvalue): + def __init__( + self, + left_variable: StateIRVariable, + nodes: Set["Node"], + call_ir: Union[InternalCall, HighLevelCall], + rvalue: StateIRVariable, + ) -> None: assert is_valid_lvalue(left_variable) assert isinstance(nodes, set) super().__init__(left_variable, nodes) @@ -12,17 +27,21 @@ def __init__(self, left_variable, nodes, call_ir, rvalue): self._rvalue_no_callback = rvalue @property - def callee_ir(self): + def callee_ir(self) -> Union[InternalCall, HighLevelCall]: return self._call_ir @property - def read(self): + def read(self) -> List[StateIRVariable]: return self.rvalues @property def rvalues(self): return self._rvalues + @rvalues.setter + def rvalues(self, vals): + self._rvalues = vals + @property def rvalue_no_callback(self): """ @@ -30,10 +49,6 @@ def rvalue_no_callback(self): """ return self._rvalue_no_callback - @rvalues.setter - def rvalues(self, vals): - self._rvalues = vals - @property def nodes(self): return self._nodes diff --git a/slither/slithir/operations/return_operation.py b/slither/slithir/operations/return_operation.py index c1ccf47d19..c21579763d 100644 --- a/slither/slithir/operations/return_operation.py +++ b/slither/slithir/operations/return_operation.py @@ -1,8 +1,11 @@ +from typing import List + from slither.core.declarations import Function from slither.slithir.operations.operation import Operation from slither.slithir.variables.tuple import TupleVariable from slither.slithir.utils.utils import is_valid_rvalue +from slither.core.variables.variable import Variable class Return(Operation): @@ -11,7 +14,7 @@ class Return(Operation): Only present as last operation in RETURN node """ - def __init__(self, values): + def __init__(self, values) -> None: # Note: Can return None # ex: return call() # where call() dont return @@ -35,7 +38,7 @@ def __init__(self, values): super().__init__() self._values = values - def _valid_value(self, value): + def _valid_value(self, value) -> bool: if isinstance(value, list): assert all(self._valid_value(v) for v in value) else: @@ -43,11 +46,11 @@ def _valid_value(self, value): return True @property - def read(self): + def read(self) -> List[Variable]: return self._unroll(self.values) @property - def values(self): + def values(self) -> List[Variable]: return self._unroll(self._values) def __str__(self): diff --git a/slither/slithir/operations/send.py b/slither/slithir/operations/send.py index 27b0c7021c..3440434195 100644 --- a/slither/slithir/operations/send.py +++ b/slither/slithir/operations/send.py @@ -1,12 +1,24 @@ +from typing import List, Union + from slither.core.declarations.solidity_variables import SolidityVariable from slither.core.variables.variable import Variable from slither.slithir.operations.call import Call from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.utils.utils import is_valid_lvalue +from slither.core.variables.local_variable import LocalVariable +from slither.slithir.variables.constant import Constant +from slither.slithir.variables.local_variable import LocalIRVariable +from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA class Send(Call, OperationWithLValue): - def __init__(self, destination, value, result): + def __init__( + self, + destination: Union[LocalVariable, LocalIRVariable], + value: Constant, + result: Union[TemporaryVariable, TemporaryVariableSSA], + ) -> None: assert is_valid_lvalue(result) assert isinstance(destination, (Variable, SolidityVariable)) super().__init__() @@ -15,19 +27,19 @@ def __init__(self, destination, value, result): self._call_value = value - def can_send_eth(self): + def can_send_eth(self) -> bool: return True @property - def call_value(self): + def call_value(self) -> Constant: return self._call_value @property - def read(self): + def read(self) -> List[Union[Constant, LocalIRVariable, LocalVariable]]: return [self.destination, self.call_value] @property - def destination(self): + def destination(self) -> Union[LocalVariable, LocalIRVariable]: return self._destination def __str__(self): diff --git a/slither/slithir/operations/solidity_call.py b/slither/slithir/operations/solidity_call.py index 628a527fe4..b059c55a67 100644 --- a/slither/slithir/operations/solidity_call.py +++ b/slither/slithir/operations/solidity_call.py @@ -1,10 +1,19 @@ -from slither.core.declarations.solidity_variables import SolidityFunction +from typing import Any, List, Union +from slither.core.declarations.solidity_variables import SolidityCustomRevert, SolidityFunction from slither.slithir.operations.call import Call from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.children.child_node import ChildNode +from slither.core.solidity_types.elementary_type import ElementaryType class SolidityCall(Call, OperationWithLValue): - def __init__(self, function, nbr_arguments, result, type_call): + def __init__( + self, + function: Union[SolidityCustomRevert, SolidityFunction], + nbr_arguments: int, + result: ChildNode, + type_call: Union[str, List[ElementaryType]], + ) -> None: assert isinstance(function, SolidityFunction) super().__init__() self._function = function @@ -13,19 +22,19 @@ def __init__(self, function, nbr_arguments, result, type_call): self._lvalue = result @property - def read(self): + def read(self) -> List[Any]: return self._unroll(self.arguments) @property - def function(self): + def function(self) -> Union[SolidityCustomRevert, SolidityFunction]: return self._function @property - def nbr_arguments(self): + def nbr_arguments(self) -> int: return self._nbr_arguments @property - def type_call(self): + def type_call(self) -> Union[str, List[ElementaryType]]: return self._type_call def __str__(self): diff --git a/slither/slithir/operations/transfer.py b/slither/slithir/operations/transfer.py index 6cfc58f07c..40f1dab3da 100644 --- a/slither/slithir/operations/transfer.py +++ b/slither/slithir/operations/transfer.py @@ -1,29 +1,33 @@ +from typing import List, Union from slither.slithir.operations.call import Call from slither.core.variables.variable import Variable from slither.core.declarations.solidity_variables import SolidityVariable +from slither.core.variables.local_variable import LocalVariable +from slither.slithir.variables.constant import Constant +from slither.slithir.variables.local_variable import LocalIRVariable class Transfer(Call): - def __init__(self, destination, value): + def __init__(self, destination: Union[LocalVariable, LocalIRVariable], value: Constant) -> None: assert isinstance(destination, (Variable, SolidityVariable)) self._destination = destination super().__init__() self._call_value = value - def can_send_eth(self): + def can_send_eth(self) -> bool: return True @property - def call_value(self): + def call_value(self) -> Constant: return self._call_value @property - def read(self): + def read(self) -> List[Union[Constant, LocalIRVariable, LocalVariable]]: return [self.destination, self.call_value] @property - def destination(self): + def destination(self) -> Union[LocalVariable, LocalIRVariable]: return self._destination def __str__(self): diff --git a/slither/slithir/operations/type_conversion.py b/slither/slithir/operations/type_conversion.py index feee46a2c6..f351f1fdd5 100644 --- a/slither/slithir/operations/type_conversion.py +++ b/slither/slithir/operations/type_conversion.py @@ -1,11 +1,24 @@ +from typing import List, Union from slither.core.declarations import Contract from slither.core.solidity_types.type import Type from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue +import slither.core.declarations.contract +from slither.core.solidity_types.elementary_type import ElementaryType +from slither.core.solidity_types.type_alias import TypeAliasContract, TypeAliasTopLevel +from slither.core.solidity_types.user_defined_type import UserDefinedType +from slither.core.source_mapping.source_mapping import SourceMapping +from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA class TypeConversion(OperationWithLValue): - def __init__(self, result, variable, variable_type): + def __init__( + self, + result: Union[TemporaryVariableSSA, TemporaryVariable], + variable: SourceMapping, + variable_type: Union[TypeAliasContract, UserDefinedType, ElementaryType, TypeAliasTopLevel], + ) -> None: super().__init__() assert is_valid_rvalue(variable) or isinstance(variable, Contract) assert is_valid_lvalue(result) @@ -16,15 +29,23 @@ def __init__(self, result, variable, variable_type): self._lvalue = result @property - def variable(self): + def variable(self) -> SourceMapping: return self._variable @property - def type(self): + def type( + self, + ) -> Union[ + TypeAliasContract, + TypeAliasTopLevel, + slither.core.declarations.contract.Contract, + UserDefinedType, + ElementaryType, + ]: return self._type @property - def read(self): + def read(self) -> List[SourceMapping]: return [self.variable] def __str__(self): diff --git a/slither/slithir/operations/unary.py b/slither/slithir/operations/unary.py index 7df55ad756..a6529d7268 100644 --- a/slither/slithir/operations/unary.py +++ b/slither/slithir/operations/unary.py @@ -1,16 +1,24 @@ import logging +from typing import List, Union from enum import Enum from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue from slither.slithir.exceptions import SlithIRError +from slither.core.expressions.unary_operation import UnaryOperationType +from slither.core.variables.local_variable import LocalVariable +from slither.slithir.variables.constant import Constant +from slither.slithir.variables.local_variable import LocalIRVariable +from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA + logger = logging.getLogger("BinaryOperationIR") class UnaryType(Enum): - BANG = 0 # ! - TILD = 1 # ~ + BANG = "!" + TILD = "~" @staticmethod def get_type(operation_type, isprefix): @@ -21,17 +29,14 @@ def get_type(operation_type, isprefix): return UnaryType.TILD raise SlithIRError(f"get_type: Unknown operation type {operation_type}") - def __str__(self): - if self == UnaryType.BANG: - return "!" - if self == UnaryType.TILD: - return "~" - - raise SlithIRError(f"str: Unknown operation type {self}") - class Unary(OperationWithLValue): - def __init__(self, result, variable, operation_type): + def __init__( + self, + result: Union[TemporaryVariableSSA, TemporaryVariable], + variable: Union[Constant, LocalIRVariable, LocalVariable], + operation_type: UnaryOperationType, + ) -> None: assert is_valid_rvalue(variable) assert is_valid_lvalue(result) super().__init__() @@ -40,20 +45,20 @@ def __init__(self, result, variable, operation_type): self._lvalue = result @property - def read(self): + def read(self) -> List[Union[Constant, LocalIRVariable, LocalVariable]]: return [self._variable] @property - def rvalue(self): + def rvalue(self) -> Union[Constant, LocalVariable]: return self._variable @property - def type(self): + def type(self) -> UnaryOperationType: return self._type @property def type_str(self): - return str(self._type) + return self._type.value def __str__(self): return f"{self.lvalue} = {self.type_str} {self.rvalue} " diff --git a/slither/slithir/operations/unpack.py b/slither/slithir/operations/unpack.py index c5a45cb9e4..a183463d4b 100644 --- a/slither/slithir/operations/unpack.py +++ b/slither/slithir/operations/unpack.py @@ -1,10 +1,20 @@ +from typing import List, Union + from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.utils.utils import is_valid_lvalue from slither.slithir.variables.tuple import TupleVariable +from slither.core.variables.local_variable_init_from_tuple import LocalVariableInitFromTuple +from slither.slithir.variables.local_variable import LocalIRVariable +from slither.slithir.variables.tuple_ssa import TupleVariableSSA class Unpack(OperationWithLValue): - def __init__(self, result, tuple_var, idx): + def __init__( + self, + result: Union[LocalVariableInitFromTuple, LocalIRVariable], + tuple_var: Union[TupleVariable, TupleVariableSSA], + idx: int, + ) -> None: assert is_valid_lvalue(result) assert isinstance(tuple_var, TupleVariable) assert isinstance(idx, int) @@ -14,15 +24,15 @@ def __init__(self, result, tuple_var, idx): self._lvalue = result @property - def read(self): + def read(self) -> List[Union[TupleVariableSSA, TupleVariable]]: return [self.tuple] @property - def tuple(self): + def tuple(self) -> Union[TupleVariable, TupleVariableSSA]: return self._tuple @property - def index(self): + def index(self) -> int: return self._idx def __str__(self): diff --git a/slither/slithir/tmp_operations/argument.py b/slither/slithir/tmp_operations/argument.py index 746bc13f2c..25ea5d0191 100644 --- a/slither/slithir/tmp_operations/argument.py +++ b/slither/slithir/tmp_operations/argument.py @@ -10,7 +10,7 @@ class ArgumentType(Enum): class Argument(Operation): - def __init__(self, argument): + def __init__(self, argument) -> None: super().__init__() self._argument = argument self._type = ArgumentType.CALL @@ -32,11 +32,11 @@ def call_id(self, c): def read(self): return [self.argument] - def set_type(self, t): + def set_type(self, t: ArgumentType) -> None: assert isinstance(t, ArgumentType) self._type = t - def get_type(self): + def get_type(self) -> ArgumentType: return self._type def __str__(self): diff --git a/slither/slithir/tmp_operations/tmp_call.py b/slither/slithir/tmp_operations/tmp_call.py index fb66411396..2137ebd819 100644 --- a/slither/slithir/tmp_operations/tmp_call.py +++ b/slither/slithir/tmp_operations/tmp_call.py @@ -1,3 +1,5 @@ +from typing import Optional, Union + from slither.core.declarations import ( Event, Contract, @@ -8,10 +10,22 @@ from slither.core.declarations.custom_error import CustomError from slither.core.variables.variable import Variable from slither.slithir.operations.lvalue import OperationWithLValue +from slither.core.source_mapping.source_mapping import SourceMapping +from slither.slithir.operations.member import Member +from slither.slithir.tmp_operations.tmp_new_array import TmpNewArray +from slither.slithir.tmp_operations.tmp_new_contract import TmpNewContract +from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.tuple import TupleVariable class TmpCall(OperationWithLValue): # pylint: disable=too-many-instance-attributes - def __init__(self, called, nbr_arguments, result, type_call): + def __init__( + self, + called: SourceMapping, + nbr_arguments: int, + result: Union[TupleVariable, TemporaryVariable], + type_call: str, + ) -> None: assert isinstance( called, ( @@ -80,18 +94,18 @@ def called(self, c): self._called = c @property - def nbr_arguments(self): + def nbr_arguments(self) -> int: return self._nbr_arguments @property - def type_call(self): + def type_call(self) -> str: return self._type_call @property - def ori(self): + def ori(self) -> Optional[Union[TmpNewContract, TmpNewArray, "TmpCall", Member]]: return self._ori - def set_ori(self, ori): + def set_ori(self, ori: Union["TmpCall", TmpNewContract, TmpNewArray, Member]) -> None: self._ori = ori def __str__(self): diff --git a/slither/slithir/tmp_operations/tmp_new_array.py b/slither/slithir/tmp_operations/tmp_new_array.py index 0da9c54ebd..efbdb6242d 100644 --- a/slither/slithir/tmp_operations/tmp_new_array.py +++ b/slither/slithir/tmp_operations/tmp_new_array.py @@ -1,9 +1,15 @@ -from slither.slithir.operations.lvalue import OperationWithLValue from slither.core.solidity_types.type import Type +from slither.slithir.operations.lvalue import OperationWithLValue +from slither.slithir.variables.temporary import TemporaryVariable class TmpNewArray(OperationWithLValue): - def __init__(self, depth, array_type, lvalue): + def __init__( + self, + depth: int, + array_type: Type, + lvalue: TemporaryVariable, + ) -> None: super().__init__() assert isinstance(array_type, Type) self._depth = depth @@ -11,7 +17,7 @@ def __init__(self, depth, array_type, lvalue): self._lvalue = lvalue @property - def array_type(self): + def array_type(self) -> Type: return self._array_type @property @@ -19,7 +25,7 @@ def read(self): return [] @property - def depth(self): + def depth(self) -> int: return self._depth def __str__(self): diff --git a/slither/slithir/tmp_operations/tmp_new_contract.py b/slither/slithir/tmp_operations/tmp_new_contract.py index 337a40bd18..5a987a7c7e 100644 --- a/slither/slithir/tmp_operations/tmp_new_contract.py +++ b/slither/slithir/tmp_operations/tmp_new_contract.py @@ -1,8 +1,9 @@ from slither.slithir.operations.lvalue import OperationWithLValue +from slither.slithir.variables.temporary import TemporaryVariable class TmpNewContract(OperationWithLValue): - def __init__(self, contract_name, lvalue): + def __init__(self, contract_name: str, lvalue: TemporaryVariable) -> None: super().__init__() self._contract_name = contract_name self._lvalue = lvalue @@ -10,7 +11,7 @@ def __init__(self, contract_name, lvalue): self._call_salt = None @property - def contract_name(self): + def contract_name(self) -> str: return self._contract_name @property diff --git a/slither/slithir/utils/ssa.py b/slither/slithir/utils/ssa.py index 934011ff4e..6baec46006 100644 --- a/slither/slithir/utils/ssa.py +++ b/slither/slithir/utils/ssa.py @@ -1,6 +1,8 @@ import logging +from typing import Any, Callable, Dict, List, Tuple, Union -from slither.core.cfg.node import NodeType +import slither.slithir.variables.tuple_ssa +from slither.core.cfg.node import Node, NodeType from slither.core.declarations import ( Contract, Enum, @@ -10,11 +12,16 @@ Structure, Event, ) +from slither.core.declarations.function_contract import FunctionContract +from slither.core.declarations.function_top_level import FunctionTopLevel +from slither.core.declarations.modifier import Modifier from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder from slither.core.solidity_types.type import Type from slither.core.variables.local_variable import LocalVariable from slither.core.variables.state_variable import StateVariable from slither.core.variables.top_level_variable import TopLevelVariable +from slither.core.variables.variable import Variable +from slither.slithir.exceptions import SlithIRError from slither.slithir.operations import ( Assignment, Binary, @@ -46,7 +53,9 @@ Unpack, Nop, ) +from slither.slithir.operations.call import Call from slither.slithir.operations.codesize import CodeSize +from slither.slithir.operations.operation import Operation from slither.slithir.variables import ( Constant, LocalIRVariable, @@ -58,7 +67,6 @@ TupleVariable, TupleVariableSSA, ) -from slither.slithir.exceptions import SlithIRError logger = logging.getLogger("SSA_Conversion") @@ -69,7 +77,9 @@ ################################################################################### -def transform_slithir_vars_to_ssa(function): +def transform_slithir_vars_to_ssa( + function: Union[FunctionContract, Modifier, FunctionTopLevel] +) -> None: """ Transform slithIR vars to SSA (TemporaryVariable, ReferenceVariable, TupleVariable) """ @@ -99,7 +109,12 @@ def transform_slithir_vars_to_ssa(function): # pylint: disable=too-many-arguments,too-many-locals,too-many-nested-blocks,too-many-statements,too-many-branches -def add_ssa_ir(function, all_state_variables_instances): +def add_ssa_ir( + function: Union[FunctionContract, Modifier, FunctionTopLevel], + all_state_variables_instances: Dict[ + str, slither.slithir.variables.state_variable.StateIRVariable + ], +) -> None: """ Add SSA version of the IR Args: @@ -200,14 +215,14 @@ def add_ssa_ir(function, all_state_variables_instances): def generate_ssa_irs( - node, - local_variables_instances, - all_local_variables_instances, - state_variables_instances, - all_state_variables_instances, - init_local_variables_instances, - visited, -): + node: Node, + local_variables_instances: Dict[str, LocalIRVariable], + all_local_variables_instances: Dict[str, LocalIRVariable], + state_variables_instances: Dict[str, StateIRVariable], + all_state_variables_instances: Dict[str, StateIRVariable], + init_local_variables_instances: Dict[str, LocalIRVariable], + visited: List[Node], +) -> None: if node in visited: return @@ -324,7 +339,14 @@ def generate_ssa_irs( ################################################################################### -def last_name(n, var, init_vars): +def last_name( + n: Node, + var: Union[ + StateIRVariable, + LocalIRVariable, + ], + init_vars: Dict[str, LocalIRVariable], +) -> Union[StateIRVariable, LocalIRVariable,]: candidates = [] # Todo optimize by creating a variables_ssa_written attribute for ir_ssa in n.irs_ssa: @@ -343,7 +365,10 @@ def last_name(n, var, init_vars): return max(candidates, key=lambda v: v.index) -def is_used_later(initial_node, variable): +def is_used_later( + initial_node: Node, + variable: Union[StateIRVariable, LocalVariable], +) -> bool: # TODO: does not handle the case where its read and written in the declaration node # It can be problematic if this happens in a loop/if structure # Ex: @@ -390,13 +415,13 @@ def is_used_later(initial_node, variable): def update_lvalue( - new_ir, - node, - local_variables_instances, - all_local_variables_instances, - state_variables_instances, - all_state_variables_instances, -): + new_ir: Operation, + node: Node, + local_variables_instances: Dict[str, LocalIRVariable], + all_local_variables_instances: Dict[str, LocalIRVariable], + state_variables_instances: Dict[str, StateIRVariable], + all_state_variables_instances: Dict[str, StateIRVariable], +) -> None: if isinstance(new_ir, OperationWithLValue): lvalue = new_ir.lvalue update_through_ref = False @@ -438,8 +463,10 @@ def update_lvalue( def initiate_all_local_variables_instances( - nodes, local_variables_instances, all_local_variables_instances -): + nodes: List[Node], + local_variables_instances: Dict[str, LocalIRVariable], + all_local_variables_instances: Dict[str, LocalIRVariable], +) -> None: for node in nodes: if node.variable_declaration: new_var = LocalIRVariable(node.variable_declaration) @@ -458,13 +485,13 @@ def initiate_all_local_variables_instances( def fix_phi_rvalues_and_storage_ref( - node, - local_variables_instances, - all_local_variables_instances, - state_variables_instances, - all_state_variables_instances, - init_local_variables_instances, -): + node: Node, + local_variables_instances: Dict[str, LocalIRVariable], + all_local_variables_instances: Dict[str, LocalIRVariable], + state_variables_instances: Dict[str, StateIRVariable], + all_state_variables_instances: Dict[str, StateIRVariable], + init_local_variables_instances: Dict[str, LocalIRVariable], +) -> None: for ir in node.irs_ssa: if isinstance(ir, (Phi)) and not ir.rvalues: variables = [ @@ -507,7 +534,11 @@ def fix_phi_rvalues_and_storage_ref( ) -def add_phi_origins(node, local_variables_definition, state_variables_definition): +def add_phi_origins( + node: Node, + local_variables_definition: Dict[str, Tuple[LocalVariable, Node]], + state_variables_definition: Dict[str, Tuple[StateVariable, Node]], +) -> None: # Add new key to local_variables_definition # The key is the variable_name @@ -558,12 +589,12 @@ def add_phi_origins(node, local_variables_definition, state_variables_definition def get( variable, - local_variables_instances, - state_variables_instances, - temporary_variables_instances, - reference_variables_instances, - tuple_variables_instances, - all_local_variables_instances, + local_variables_instances: Dict[str, LocalIRVariable], + state_variables_instances: Dict[str, StateIRVariable], + temporary_variables_instances: Dict[int, TemporaryVariableSSA], + reference_variables_instances: Dict[int, ReferenceVariableSSA], + tuple_variables_instances: Dict[int, TupleVariableSSA], + all_local_variables_instances: Dict[str, LocalIRVariable], ): # variable can be None # for example, on LowLevelCall, ir.lvalue can be none @@ -625,14 +656,14 @@ def get( return variable -def get_variable(ir, f, *instances): +def get_variable(ir: Operation, f: Callable, *instances): # pylint: disable=no-value-for-parameter variable = f(ir) variable = get(variable, *instances) return variable -def _get_traversal(values, *instances): +def _get_traversal(values: List[Any], *instances) -> List[Any]: ret = [] # pylint: disable=no-value-for-parameter for v in values: @@ -644,11 +675,19 @@ def _get_traversal(values, *instances): return ret -def get_arguments(ir, *instances): +def get_arguments(ir: Call, *instances) -> List[Any]: return _get_traversal(ir.arguments, *instances) -def get_rec_values(ir, f, *instances): +def get_rec_values( + ir: Union[ + InitArray, + Return, + NewArray, + ], + f: Callable, + *instances, +) -> List[Variable]: # Use by InitArray and NewArray # Potential recursive array(s) ori_init_values = f(ir) @@ -656,7 +695,7 @@ def get_rec_values(ir, f, *instances): return _get_traversal(ori_init_values, *instances) -def copy_ir(ir, *instances): +def copy_ir(ir: Operation, *instances) -> Operation: """ Args: ir (Operation) diff --git a/slither/slithir/utils/utils.py b/slither/slithir/utils/utils.py index 7bebc0a804..0a50f8e50e 100644 --- a/slither/slithir/utils/utils.py +++ b/slither/slithir/utils/utils.py @@ -8,9 +8,10 @@ from slither.slithir.variables.constant import Constant from slither.slithir.variables.reference import ReferenceVariable from slither.slithir.variables.tuple import TupleVariable +from slither.core.source_mapping.source_mapping import SourceMapping -def is_valid_rvalue(v): +def is_valid_rvalue(v: SourceMapping) -> bool: return isinstance( v, ( @@ -25,7 +26,7 @@ def is_valid_rvalue(v): ) -def is_valid_lvalue(v): +def is_valid_lvalue(v) -> bool: return isinstance( v, ( diff --git a/slither/slithir/variables/constant.py b/slither/slithir/variables/constant.py index 5da2d9cc0d..ddfc9e0546 100644 --- a/slither/slithir/variables/constant.py +++ b/slither/slithir/variables/constant.py @@ -1,4 +1,5 @@ from functools import total_ordering +from typing import Optional, Union from slither.core.solidity_types.elementary_type import ElementaryType, Int, Uint from slither.slithir.variables.variable import SlithIRVariable @@ -9,8 +10,11 @@ @total_ordering class Constant(SlithIRVariable): def __init__( - self, val, constant_type=None, subdenomination=None - ): # pylint: disable=too-many-branches + self, + val: Union[int, str], + constant_type: Optional[ElementaryType] = None, + subdenomination: Optional[str] = None, + ) -> None: # pylint: disable=too-many-branches super().__init__() assert isinstance(val, str) @@ -38,7 +42,7 @@ def __init__( self._val = val @property - def value(self): + def value(self) -> Union[bool, int, str]: """ Return the value. If the expression was an hexadecimal delcared as hex'...' @@ -49,21 +53,21 @@ def value(self): return self._val @property - def original_value(self): + def original_value(self) -> str: """ Return the string representation of the value :return: str """ return self._original_value - def __str__(self): + def __str__(self) -> str: return str(self.value) @property - def name(self): + def name(self) -> str: return str(self) - def __eq__(self, other): + def __eq__(self, other: Union["Constant", str]) -> bool: return self.value == other def __ne__(self, other): diff --git a/slither/slithir/variables/local_variable.py b/slither/slithir/variables/local_variable.py index b78500f14a..eb32d40247 100644 --- a/slither/slithir/variables/local_variable.py +++ b/slither/slithir/variables/local_variable.py @@ -1,12 +1,14 @@ +from typing import Set from slither.core.variables.local_variable import LocalVariable from slither.slithir.variables.temporary import TemporaryVariable from slither.slithir.variables.variable import SlithIRVariable +from slither.slithir.variables.state_variable import StateIRVariable class LocalIRVariable( LocalVariable, SlithIRVariable ): # pylint: disable=too-many-instance-attributes - def __init__(self, local_variable): + def __init__(self, local_variable: LocalVariable) -> None: assert isinstance(local_variable, LocalVariable) super().__init__() @@ -30,7 +32,7 @@ def __init__(self, local_variable): # Additional field # points to state variables - self._refers_to = set() + self._refers_to: Set[StateIRVariable] = set() # keep un-ssa version if isinstance(local_variable, LocalIRVariable): @@ -57,10 +59,10 @@ def refers_to(self, variables): self._refers_to = variables @property - def non_ssa_version(self): + def non_ssa_version(self) -> LocalVariable: return self._non_ssa_version - def add_refers_to(self, variable): + def add_refers_to(self, variable: StateIRVariable) -> None: # It is a temporaryVariable if its the return of a new .. # ex: string[] memory dynargs = new string[](1); assert isinstance(variable, (SlithIRVariable, TemporaryVariable)) diff --git a/slither/slithir/variables/reference.py b/slither/slithir/variables/reference.py index 100886f2f6..95802b7e21 100644 --- a/slither/slithir/variables/reference.py +++ b/slither/slithir/variables/reference.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING +from typing import Optional, TYPE_CHECKING from slither.core.children.child_node import ChildNode from slither.core.declarations import Contract, Enum, SolidityVariable, Function @@ -9,7 +9,7 @@ class ReferenceVariable(ChildNode, Variable): - def __init__(self, node: "Node", index=None): + def __init__(self, node: "Node", index: Optional[int] = None) -> None: super().__init__() if index is None: self._index = node.compilation_unit.counter_slithir_reference @@ -35,13 +35,6 @@ def points_to(self): """ return self._points_to - @property - def points_to_origin(self): - points = self.points_to - while isinstance(points, ReferenceVariable): - points = points.points_to - return points - @points_to.setter def points_to(self, points_to): # Can only be a rvalue of @@ -56,17 +49,24 @@ def points_to(self, points_to): self._points_to = points_to @property - def name(self): + def points_to_origin(self): + points = self.points_to + while isinstance(points, ReferenceVariable): + points = points.points_to + return points + + @property + def name(self) -> str: return f"REF_{self.index}" # overide of core.variables.variables # reference can have Function has a type # to handle the function selector - def set_type(self, t): + def set_type(self, t) -> None: if not isinstance(t, Function): super().set_type(t) else: self._type = t - def __str__(self): + def __str__(self) -> str: return self.name diff --git a/slither/slithir/variables/reference_ssa.py b/slither/slithir/variables/reference_ssa.py index 3e591555d4..6359b57229 100644 --- a/slither/slithir/variables/reference_ssa.py +++ b/slither/slithir/variables/reference_ssa.py @@ -3,15 +3,17 @@ It is similar to the non-SSA version of slithIR as the ReferenceVariable are in SSA form in both version """ +from typing import Union from slither.slithir.variables.reference import ReferenceVariable +from slither.slithir.variables.tuple import TupleVariable class ReferenceVariableSSA(ReferenceVariable): # pylint: disable=too-few-public-methods - def __init__(self, reference): + def __init__(self, reference: ReferenceVariable) -> None: super().__init__(reference.node, reference.index) self._non_ssa_version = reference @property - def non_ssa_version(self): + def non_ssa_version(self) -> Union[ReferenceVariable, TupleVariable]: return self._non_ssa_version diff --git a/slither/slithir/variables/state_variable.py b/slither/slithir/variables/state_variable.py index 0f92d86870..7bb3a4077d 100644 --- a/slither/slithir/variables/state_variable.py +++ b/slither/slithir/variables/state_variable.py @@ -5,7 +5,7 @@ class StateIRVariable( StateVariable, SlithIRVariable ): # pylint: disable=too-many-instance-attributes - def __init__(self, state_variable): + def __init__(self, state_variable: StateVariable) -> None: assert isinstance(state_variable, StateVariable) super().__init__() @@ -38,7 +38,7 @@ def index(self, idx): self._index = idx @property - def non_ssa_version(self): + def non_ssa_version(self) -> StateVariable: return self._non_ssa_version @property diff --git a/slither/slithir/variables/temporary.py b/slither/slithir/variables/temporary.py index e1552cf6d5..842ab24b6e 100644 --- a/slither/slithir/variables/temporary.py +++ b/slither/slithir/variables/temporary.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Optional +from typing import Optional, TYPE_CHECKING from slither.core.children.child_node import ChildNode from slither.core.variables.variable import Variable @@ -8,7 +8,7 @@ class TemporaryVariable(ChildNode, Variable): - def __init__(self, node: "Node", index=None, location: Optional[str] = None): + def __init__(self, node: "Node", index=None, location: Optional[str] = None) -> None: super().__init__() if index is None: self._index = node.compilation_unit.counter_slithir_temporary @@ -31,8 +31,8 @@ def index(self, idx): self._index = idx @property - def name(self): + def name(self) -> str: return f"TMP_{self.index}" - def __str__(self): + def __str__(self) -> str: return self.name diff --git a/slither/slithir/variables/temporary_ssa.py b/slither/slithir/variables/temporary_ssa.py index 3dc772b75e..0d8fb8e3cb 100644 --- a/slither/slithir/variables/temporary_ssa.py +++ b/slither/slithir/variables/temporary_ssa.py @@ -3,15 +3,18 @@ It is similar to the non-SSA version of slithIR as the TemporaryVariable are in SSA form in both version """ +from typing import Union from slither.slithir.variables.temporary import TemporaryVariable +from slither.slithir.variables.reference import ReferenceVariable +from slither.slithir.variables.tuple import TupleVariable class TemporaryVariableSSA(TemporaryVariable): # pylint: disable=too-few-public-methods - def __init__(self, temporary): + def __init__(self, temporary: TemporaryVariable) -> None: super().__init__(temporary.node, temporary.index) self._non_ssa_version = temporary @property - def non_ssa_version(self): + def non_ssa_version(self) -> Union[TemporaryVariable, TupleVariable, ReferenceVariable]: return self._non_ssa_version diff --git a/slither/slithir/variables/tuple.py b/slither/slithir/variables/tuple.py index 537f91d420..dc085347e9 100644 --- a/slither/slithir/variables/tuple.py +++ b/slither/slithir/variables/tuple.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING +from typing import Optional, TYPE_CHECKING from slither.core.children.child_node import ChildNode from slither.slithir.variables.variable import SlithIRVariable @@ -8,7 +8,7 @@ class TupleVariable(ChildNode, SlithIRVariable): - def __init__(self, node: "Node", index=None): + def __init__(self, node: "Node", index: Optional[int] = None) -> None: super().__init__() if index is None: self._index = node.compilation_unit.counter_slithir_tuple @@ -27,8 +27,8 @@ def index(self, idx): self._index = idx @property - def name(self): + def name(self) -> str: return f"TUPLE_{self.index}" - def __str__(self): + def __str__(self) -> str: return self.name diff --git a/slither/slithir/variables/tuple_ssa.py b/slither/slithir/variables/tuple_ssa.py index 50a4b672bf..881feb1d68 100644 --- a/slither/slithir/variables/tuple_ssa.py +++ b/slither/slithir/variables/tuple_ssa.py @@ -7,7 +7,7 @@ class TupleVariableSSA(TupleVariable): # pylint: disable=too-few-public-methods - def __init__(self, t): + def __init__(self, t: TupleVariable) -> None: super().__init__(t.node, t.index) self._non_ssa_version = t diff --git a/slither/slithir/variables/variable.py b/slither/slithir/variables/variable.py index 8e2cb145c2..a1a1a6df9b 100644 --- a/slither/slithir/variables/variable.py +++ b/slither/slithir/variables/variable.py @@ -2,7 +2,7 @@ class SlithIRVariable(Variable): - def __init__(self): + def __init__(self) -> None: super().__init__() self._index = 0 diff --git a/slither/solc_parsing/cfg/node.py b/slither/solc_parsing/cfg/node.py index d7009fddad..b1380553b0 100644 --- a/slither/solc_parsing/cfg/node.py +++ b/slither/solc_parsing/cfg/node.py @@ -1,4 +1,4 @@ -from typing import Optional, Dict +from typing import Union, Optional, Dict, TYPE_CHECKING from slither.core.cfg.node import Node from slither.core.cfg.node import NodeType @@ -12,9 +12,13 @@ from slither.visitors.expression.read_var import ReadVar from slither.visitors.expression.write_var import WriteVar +if TYPE_CHECKING: + from slither.solc_parsing.declarations.function import FunctionSolc + from slither.solc_parsing.declarations.modifier import ModifierSolc + class NodeSolc: - def __init__(self, node: Node): + def __init__(self, node: Node) -> None: self._unparsed_expression: Optional[Dict] = None self._node = node @@ -22,11 +26,11 @@ def __init__(self, node: Node): def underlying_node(self) -> Node: return self._node - def add_unparsed_expression(self, expression: Dict): + def add_unparsed_expression(self, expression: Dict) -> None: assert self._unparsed_expression is None self._unparsed_expression = expression - def analyze_expressions(self, caller_context): + def analyze_expressions(self, caller_context: Union["FunctionSolc", "ModifierSolc"]) -> None: if self._node.type == NodeType.VARIABLE and not self._node.expression: self._node.add_expression(self._node.variable_declaration.expression) if self._unparsed_expression: diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index 42648c3315..516e3d3582 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -1,6 +1,6 @@ import logging import re -from typing import List, Dict, Callable, TYPE_CHECKING, Union, Set +from typing import Any, List, Dict, Callable, TYPE_CHECKING, Union, Set from slither.core.declarations import Modifier, Event, EnumContract, StructureContract, Function from slither.core.declarations.contract import Contract @@ -23,14 +23,15 @@ if TYPE_CHECKING: from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc - from slither.core.slither_core import SlitherCore from slither.core.compilation_unit import SlitherCompilationUnit # pylint: disable=too-many-instance-attributes,import-outside-toplevel,too-many-nested-blocks,too-many-public-methods class ContractSolc(CallerContextExpression): - def __init__(self, slither_parser: "SlitherCompilationUnitSolc", contract: Contract, data): + def __init__( + self, slither_parser: "SlitherCompilationUnitSolc", contract: Contract, data: Dict[str, Any] + ) -> None: # assert slitherSolc.solc_version.startswith('0.4') self._contract = contract @@ -87,7 +88,7 @@ def __init__(self, slither_parser: "SlitherCompilationUnitSolc", contract: Contr def is_analyzed(self) -> bool: return self._is_analyzed - def set_is_analyzed(self, is_analyzed: bool): + def set_is_analyzed(self, is_analyzed: bool) -> None: self._is_analyzed = is_analyzed @property @@ -131,7 +132,7 @@ def enums_not_parsed(self) -> List[Dict]: def get_key(self) -> str: return self._slither_parser.get_key() - def get_children(self, key="nodes") -> str: + def get_children(self, key: str = "nodes") -> str: if self.is_compact_ast: return key return "children" @@ -151,7 +152,7 @@ def is_compact_ast(self) -> bool: ################################################################################### ################################################################################### - def _parse_contract_info(self): + def _parse_contract_info(self) -> None: if self.is_compact_ast: attributes = self._data else: @@ -164,6 +165,8 @@ def _parse_contract_info(self): elif attributes["contractKind"] == "library": self._contract.is_library = True self._contract.contract_kind = attributes["contractKind"] + self._contract.is_fully_implemented = attributes["fullyImplemented"] + if "abstract" in attributes: self._contract._is_abstract = attributes["abstract"] == True @@ -181,7 +184,7 @@ def _parse_contract_info(self): "name" ] - def _parse_base_contract_info(self): # pylint: disable=too-many-branches + def _parse_base_contract_info(self) -> None: # pylint: disable=too-many-branches # Parse base contracts (immediate, non-linearized) if self.is_compact_ast: # Parse base contracts + constructors in compact-ast @@ -239,7 +242,7 @@ def _parse_base_contract_info(self): # pylint: disable=too-many-branches ): self.baseConstructorContractsCalled.append(referencedDeclaration) - def _parse_contract_items(self): + def _parse_contract_items(self) -> None: # pylint: disable=too-many-branches if not self.get_children() in self._data: # empty contract return @@ -292,7 +295,7 @@ def _parse_type_alias(self, item: Dict) -> None: self._contract.file_scope.user_defined_types[alias] = user_defined_type self._contract.file_scope.user_defined_types[alias_canonical] = user_defined_type - def _parse_struct(self, struct: Dict): + def _parse_struct(self, struct: Dict) -> None: st = StructureContract(self._contract.compilation_unit) st.set_contract(self._contract) @@ -302,7 +305,7 @@ def _parse_struct(self, struct: Dict): self._contract.structures_as_dict[st.name] = st self._structures_parser.append(st_parser) - def parse_structs(self): + def parse_structs(self) -> None: for father in self._contract.inheritance_reverse: self._contract.structures_as_dict.update(father.structures_as_dict) @@ -310,7 +313,7 @@ def parse_structs(self): self._parse_struct(struct) self._structuresNotParsed = None - def _parse_custom_error(self, custom_error: Dict): + def _parse_custom_error(self, custom_error: Dict) -> None: ce = CustomErrorContract(self.compilation_unit) ce.set_contract(self._contract) ce.set_offset(custom_error["src"], self.compilation_unit) @@ -319,7 +322,7 @@ def _parse_custom_error(self, custom_error: Dict): self._contract.custom_errors_as_dict[ce.name] = ce self._custom_errors_parser.append(ce_parser) - def parse_custom_errors(self): + def parse_custom_errors(self) -> None: for father in self._contract.inheritance_reverse: self._contract.custom_errors_as_dict.update(father.custom_errors_as_dict) @@ -327,7 +330,7 @@ def parse_custom_errors(self): self._parse_custom_error(custom_error) self._customErrorParsed = None - def parse_state_variables(self): + def parse_state_variables(self) -> None: for father in self._contract.inheritance_reverse: self._contract.variables_as_dict.update( { @@ -355,7 +358,7 @@ def parse_state_variables(self): self._contract.variables_as_dict[var.name] = var self._contract.add_variables_ordered([var]) - def _parse_modifier(self, modifier_data: Dict): + def _parse_modifier(self, modifier_data: Dict) -> None: modif = Modifier(self._contract.compilation_unit) modif.set_offset(modifier_data["src"], self._contract.compilation_unit) modif.set_contract(self._contract) @@ -368,12 +371,12 @@ def _parse_modifier(self, modifier_data: Dict): self._slither_parser.add_function_or_modifier_parser(modif_parser) - def parse_modifiers(self): + def parse_modifiers(self) -> None: for modifier in self._modifiersNotParsed: self._parse_modifier(modifier) self._modifiersNotParsed = None - def _parse_function(self, function_data: Dict): + def _parse_function(self, function_data: Dict) -> None: func = FunctionContract(self._contract.compilation_unit) func.set_offset(function_data["src"], self._contract.compilation_unit) func.set_contract(self._contract) @@ -386,7 +389,7 @@ def _parse_function(self, function_data: Dict): self._slither_parser.add_function_or_modifier_parser(func_parser) - def parse_functions(self): + def parse_functions(self) -> None: for function in self._functionsNotParsed: self._parse_function(function) @@ -406,21 +409,21 @@ def log_incorrect_parsing(self, error: str) -> None: LOGGER.error(error) self._contract.is_incorrectly_constructed = True - def analyze_content_modifiers(self): + def analyze_content_modifiers(self) -> None: try: for modifier_parser in self._modifiers_parser: modifier_parser.analyze_content() except (VariableNotFound, KeyError) as e: self.log_incorrect_parsing(f"Missing modifier {e}") - def analyze_content_functions(self): + def analyze_content_functions(self) -> None: try: for function_parser in self._functions_parser: function_parser.analyze_content() except (VariableNotFound, KeyError, ParsingError) as e: self.log_incorrect_parsing(f"Missing function {e}") - def analyze_params_modifiers(self): + def analyze_params_modifiers(self) -> None: try: elements_no_params = self._modifiers_no_params getter = lambda c: c.modifiers_parser @@ -440,7 +443,7 @@ def analyze_params_modifiers(self): self.log_incorrect_parsing(f"Missing params {e}") self._modifiers_no_params = [] - def analyze_params_functions(self): + def analyze_params_functions(self) -> None: try: elements_no_params = self._functions_no_params getter = lambda c: c.functions_parser @@ -468,7 +471,7 @@ def _analyze_params_element( # pylint: disable=too-many-arguments explored_reference_id: Set[str], parser: List[FunctionSolc], all_elements: Dict[str, Function], - ): + ) -> None: elem = Cls(self._contract.compilation_unit) elem.set_contract(self._contract) underlying_function = element_parser.underlying_function @@ -569,7 +572,7 @@ def _analyze_params_elements( # pylint: disable=too-many-arguments,too-many-loc self.log_incorrect_parsing(f"Missing params {e}") return all_elements - def analyze_constant_state_variables(self): + def analyze_constant_state_variables(self) -> None: for var_parser in self._variables_parser: if var_parser.underlying_variable.is_constant: # cant parse constant expression based on function calls @@ -578,7 +581,7 @@ def analyze_constant_state_variables(self): except (VariableNotFound, KeyError) as e: LOGGER.error(e) - def analyze_state_variables(self): + def analyze_state_variables(self) -> None: try: for var_parser in self._variables_parser: var_parser.analyze(self) @@ -586,7 +589,7 @@ def analyze_state_variables(self): except (VariableNotFound, KeyError) as e: self.log_incorrect_parsing(f"Missing state variable {e}") - def analyze_using_for(self): # pylint: disable=too-many-branches + def analyze_using_for(self) -> None: # pylint: disable=too-many-branches try: for father in self._contract.inheritance: self._contract.using_for.update(father.using_for) @@ -638,7 +641,7 @@ def analyze_using_for(self): # pylint: disable=too-many-branches except (VariableNotFound, KeyError) as e: self.log_incorrect_parsing(f"Missing using for {e}") - def _analyze_function_list(self, function_list: List, type_name: Type, uf : Structure): + def _analyze_function_list(self, function_list: List, type_name: Type, uf : Structure) -> None: for f in function_list: full_name_split = f["function"]["name"].split(".") if len(full_name_split) == 1: @@ -657,7 +660,7 @@ def _analyze_function_list(self, function_list: List, type_name: Type, uf : Stru function_name = full_name_split[2] self._analyze_library_function(library_name, function_name, type_name, uf) - def _check_aliased_import(self, first_part: str, function_name: str, type_name: Type, uf: Structure): + def _check_aliased_import(self, first_part: str, function_name: str, type_name: Type, uf: Structure) -> None: # We check if the first part appear as alias for an import # if it is then function_name must be a top level function # otherwise it's a library function @@ -667,7 +670,7 @@ def _check_aliased_import(self, first_part: str, function_name: str, type_name: return self._analyze_library_function(first_part, function_name, type_name, uf) - def _analyze_top_level_function(self, function_name: str, type_name: Type, uf: Structure): + def _analyze_top_level_function(self, function_name: str, type_name: Type, uf: Structure) -> None: for tl_function in self.compilation_unit.functions_top_level: if tl_function.name == function_name: self._contract.using_for[type_name].append(tl_function) @@ -693,7 +696,7 @@ def _analyze_library_function( f"Contract level using for: Library {library_name} - function {function_name} not found" ) - def analyze_enums(self): + def analyze_enums(self) -> None: try: for father in self._contract.inheritance: self._contract.enums_as_dict.update(father.enums_as_dict) @@ -706,7 +709,19 @@ def analyze_enums(self): except (VariableNotFound, KeyError) as e: self.log_incorrect_parsing(f"Missing enum {e}") - def _analyze_enum(self, enum): + def _analyze_enum( + self, + enum: Dict[ + str, + Union[ + str, + int, + List[Dict[str, Union[int, str]]], + Dict[str, str], + List[Dict[str, Union[Dict[str, str], int, str]]], + ], + ], + ) -> None: # Enum can be parsed in one pass if self.is_compact_ast: name = enum["name"] @@ -730,21 +745,21 @@ def _analyze_enum(self, enum): new_enum.set_offset(enum["src"], self._contract.compilation_unit) self._contract.enums_as_dict[canonicalName] = new_enum - def _analyze_struct(self, struct: StructureContractSolc): # pylint: disable=no-self-use + def _analyze_struct(self, struct: StructureContractSolc) -> None: # pylint: disable=no-self-use struct.analyze() - def analyze_structs(self): + def analyze_structs(self) -> None: try: for struct in self._structures_parser: self._analyze_struct(struct) except (VariableNotFound, KeyError) as e: self.log_incorrect_parsing(f"Missing struct {e}") - def analyze_custom_errors(self): + def analyze_custom_errors(self) -> None: for custom_error in self._custom_errors_parser: custom_error.analyze_params() - def analyze_events(self): + def analyze_events(self) -> None: try: for father in self._contract.inheritance_reverse: self._contract.events_as_dict.update(father.events_as_dict) @@ -787,12 +802,35 @@ def delete_content(self): self._customErrorParsed = [] def _handle_comment(self, attributes: Dict) -> None: + """ + Save the contract comment in self.comments + And handle custom slither comments + + Args: + attributes: + + Returns: + + """ + # Old solc versions store the comment in attributes["documentation"] + # More recent ones store it in attributes["documentation"]["text"] if ( "documentation" in attributes and attributes["documentation"] is not None - and "text" in attributes["documentation"] + and ( + "text" in attributes["documentation"] + or isinstance(attributes["documentation"], str) + ) ): - candidates = attributes["documentation"]["text"].replace("\n", ",").split(",") + text = ( + attributes["documentation"] + if isinstance(attributes["documentation"], str) + else attributes["documentation"]["text"] + ) + self._contract.comments = text + + # Look for custom comments + candidates = text.replace("\n", ",").split(",") for candidate in candidates: if "@custom:security isDelegatecallProxy" in candidate: diff --git a/slither/solc_parsing/declarations/custom_error.py b/slither/solc_parsing/declarations/custom_error.py index 83f6f0e5c5..8cd4592623 100644 --- a/slither/solc_parsing/declarations/custom_error.py +++ b/slither/solc_parsing/declarations/custom_error.py @@ -22,7 +22,7 @@ def __init__( custom_error: CustomError, custom_error_data: dict, slither_parser: "SlitherCompilationUnitSolc", - ): + ) -> None: self._slither_parser: "SlitherCompilationUnitSolc" = slither_parser self._custom_error = custom_error custom_error.name = custom_error_data["name"] @@ -32,7 +32,7 @@ def __init__( custom_error_data = custom_error_data["attributes"] self._custom_error_data = custom_error_data - def analyze_params(self): + def analyze_params(self) -> None: # Can be re-analyzed due to inheritance if self._params_was_analyzed: return @@ -68,7 +68,7 @@ def get_children(self, key: str) -> str: return key return "children" - def _parse_params(self, params: Dict): + def _parse_params(self, params: Dict) -> None: assert params[self.get_key()] == "ParameterList" if self._slither_parser.is_compact_ast: diff --git a/slither/solc_parsing/declarations/event.py b/slither/solc_parsing/declarations/event.py index 1f8904fc18..6531e65365 100644 --- a/slither/solc_parsing/declarations/event.py +++ b/slither/solc_parsing/declarations/event.py @@ -16,7 +16,7 @@ class EventSolc: Event class """ - def __init__(self, event: Event, event_data: Dict, contract_parser: "ContractSolc"): + def __init__(self, event: Event, event_data: Dict, contract_parser: "ContractSolc") -> None: self._event = event event.set_contract(contract_parser.underlying_contract) @@ -43,7 +43,7 @@ def __init__(self, event: Event, event_data: Dict, contract_parser: "ContractSol def is_compact_ast(self) -> bool: return self._parser_contract.is_compact_ast - def analyze(self, contract: "ContractSolc"): + def analyze(self, contract: "ContractSolc") -> None: for elem_to_parse in self._elemsNotParsed: elem = EventVariable() # Todo: check if the source offset is always here diff --git a/slither/solc_parsing/declarations/function.py b/slither/solc_parsing/declarations/function.py index ccaadfa29d..b81fbd558e 100644 --- a/slither/solc_parsing/declarations/function.py +++ b/slither/solc_parsing/declarations/function.py @@ -1,6 +1,6 @@ import logging -import pdb -from typing import Dict, Literal, Optional, Union, List, TYPE_CHECKING +from typing import Dict, Optional, Union, List, TYPE_CHECKING, Tuple +from typing import Literal from slither.core.expressions.tuple_expression import TupleExpression @@ -27,10 +27,10 @@ LocalVariableInitFromTupleSolc, ) from slither.solc_parsing.variables.variable_declaration import MultipleVariablesDeclaration -from slither.solc_parsing.yul.parse_yul import YulBlock from slither.utils.expression_manipulations import SplitTernaryExpression from slither.visitors.expression.export_values import ExportValues from slither.visitors.expression.has_conditional import HasConditional +from slither.solc_parsing.yul.parse_yul import YulBlock from slither.visitors.expression.expression import ExpressionVisitor from slither.core.expressions.assignment_operation import AssignmentOperationType from slither.core.expressions.identifier import Identifier @@ -89,7 +89,7 @@ def __init__( function_data: Dict, contract_parser: Optional["ContractSolc"], slither_parser: "SlitherCompilationUnitSolc", - ): + ) -> None: self._slither_parser: "SlitherCompilationUnitSolc" = slither_parser self._contract_parser = contract_parser self._function = function @@ -177,7 +177,7 @@ def variables_renamed( def _add_local_variable( self, local_var_parser: Union[LocalVariableSolc, LocalVariableInitFromTupleSolc] - ): + ) -> None: # If two local variables have the same name # We add a suffix to the new variable # This is done to prevent collision during SSA translation @@ -209,7 +209,7 @@ def _add_local_variable( def function_not_parsed(self) -> Dict: return self._functionNotParsed - def _analyze_type(self): + def _analyze_type(self) -> None: """ Analyz the type of the function Myst be called in the constructor as the name might change according to the function's type @@ -235,7 +235,7 @@ def _analyze_type(self): if self._function.name == self._function.contract_declarer.name: self._function.function_type = FunctionType.CONSTRUCTOR - def _analyze_attributes(self): + def _analyze_attributes(self) -> None: if self.is_compact_ast: attributes = self._functionNotParsed else: @@ -480,7 +480,7 @@ def _parse_while(self, whilte_statement: Dict, node: NodeSolc) -> NodeSolc: def _parse_for_compact_ast( # pylint: disable=no-self-use self, statement: Dict - ) -> (Optional[Dict], Optional[Dict], Optional[Dict], Dict): + ) -> Tuple[Optional[Dict], Optional[Dict], Optional[Dict], Dict]: body = statement["body"] init_expression = statement.get("initializationExpression", None) condition = statement.get("condition", None) @@ -490,7 +490,7 @@ def _parse_for_compact_ast( # pylint: disable=no-self-use def _parse_for_legacy_ast( self, statement: Dict - ) -> (Optional[Dict], Optional[Dict], Optional[Dict], Dict): + ) -> Tuple[Optional[Dict], Optional[Dict], Optional[Dict], Dict]: # if we're using an old version of solc (anything below and including 0.4.11) or if the user # explicitly enabled compact ast, we might need to make some best-effort guesses children = statement[self.get_children("children")] @@ -1181,7 +1181,7 @@ def _parse_statement( return node - def _parse_block(self, block: Dict, node: NodeSolc, check_arithmetic: bool = False): + def _parse_block(self, block: Dict, node: NodeSolc, check_arithmetic: bool = False) -> NodeSolc: """ Return: Node @@ -1216,7 +1216,7 @@ def _parse_unchecked_block(self, block: Dict, node: NodeSolc): node = self._parse_statement(statement, node, new_scope) return node - def _parse_cfg(self, cfg: Dict): + def _parse_cfg(self, cfg: Dict) -> None: assert cfg[self.get_key()] == "Block" @@ -1306,7 +1306,7 @@ def _find_start_loop(self, node: Node, visited: List[Node]) -> Optional[Node]: return None - def _fix_break_node(self, node: Node): + def _fix_break_node(self, node: Node) -> None: end_node = self._find_end_loop(node, [], 0) if not end_node: @@ -1322,7 +1322,7 @@ def _fix_break_node(self, node: Node): node.set_sons([end_node]) end_node.add_father(node) - def _fix_continue_node(self, node: Node): + def _fix_continue_node(self, node: Node) -> None: start_node = self._find_start_loop(node, []) if not start_node: @@ -1335,14 +1335,14 @@ def _fix_continue_node(self, node: Node): node.set_sons([dest]) dest.add_father(node) - def _fix_try(self, node: Node): + def _fix_try(self, node: Node) -> None: end_node = next((son for son in node.sons if son.type != NodeType.CATCH), None) if end_node: for son in node.sons: if son.type == NodeType.CATCH: self._fix_catch(son, end_node) - def _fix_catch(self, node: Node, end_node: Node): + def _fix_catch(self, node: Node, end_node: Node) -> None: if not node.sons: link_nodes(node, end_node) else: diff --git a/slither/solc_parsing/declarations/modifier.py b/slither/solc_parsing/declarations/modifier.py index e55487612b..ea7af00b37 100644 --- a/slither/solc_parsing/declarations/modifier.py +++ b/slither/solc_parsing/declarations/modifier.py @@ -23,7 +23,7 @@ def __init__( function_data: Dict, contract_parser: "ContractSolc", slither_parser: "SlitherCompilationUnitSolc", - ): + ) -> None: super().__init__(modifier, function_data, contract_parser, slither_parser) # _modifier is equal to _function, but keep it here to prevent # confusion for mypy in underlying_function @@ -33,7 +33,7 @@ def __init__( def underlying_function(self) -> Modifier: return self._modifier - def analyze_params(self): + def analyze_params(self) -> None: # Can be re-analyzed due to inheritance if self._params_was_analyzed: return @@ -55,7 +55,7 @@ def analyze_params(self): if params: self._parse_params(params) - def analyze_content(self): + def analyze_content(self) -> None: if self._content_was_analyzed: return diff --git a/slither/solc_parsing/declarations/structure_contract.py b/slither/solc_parsing/declarations/structure_contract.py index 9c3784ea93..c48c73c4f6 100644 --- a/slither/solc_parsing/declarations/structure_contract.py +++ b/slither/solc_parsing/declarations/structure_contract.py @@ -23,7 +23,7 @@ def __init__( # pylint: disable=too-many-arguments st: Structure, struct: Dict, contract_parser: "ContractSolc", - ): + ) -> None: if contract_parser.is_compact_ast: name = struct["name"] @@ -45,7 +45,7 @@ def __init__( # pylint: disable=too-many-arguments self._elemsNotParsed = children - def analyze(self): + def analyze(self) -> None: for elem_to_parse in self._elemsNotParsed: elem = StructureVariable() elem.set_structure(self._structure) diff --git a/slither/solc_parsing/declarations/structure_top_level.py b/slither/solc_parsing/declarations/structure_top_level.py index 1597ad44e1..6dcca19d49 100644 --- a/slither/solc_parsing/declarations/structure_top_level.py +++ b/slither/solc_parsing/declarations/structure_top_level.py @@ -25,7 +25,7 @@ def __init__( # pylint: disable=too-many-arguments st: StructureTopLevel, struct: Dict, slither_parser: "SlitherCompilationUnitSolc", - ): + ) -> None: if slither_parser.is_compact_ast: name = struct["name"] @@ -47,7 +47,7 @@ def __init__( # pylint: disable=too-many-arguments self._elemsNotParsed = children - def analyze(self): + def analyze(self) -> None: for elem_to_parse in self._elemsNotParsed: elem = StructureVariable() elem.set_structure(self._structure) diff --git a/slither/solc_parsing/declarations/using_for_top_level.py b/slither/solc_parsing/declarations/using_for_top_level.py index 16e3666b0f..707ad83ac7 100644 --- a/slither/solc_parsing/declarations/using_for_top_level.py +++ b/slither/solc_parsing/declarations/using_for_top_level.py @@ -49,7 +49,7 @@ def analyze(self) -> None: type_name = parse_type(self._type_name, self) self._using_for.using_for[type_name] = [] - if self._library_name is not None: + if self._library_name: library_name = parse_type(self._library_name, self) self._using_for.using_for[type_name].append(library_name) self._propagate_global(type_name) @@ -77,7 +77,7 @@ def _check_aliased_import( first_part: str, function_name: str, type_name: Union[TypeAliasTopLevel, UserDefinedType], - ): + ) -> None: # We check if the first part appear as alias for an import # if it is then function_name must be a top level function # otherwise it's a library function @@ -90,8 +90,13 @@ def _check_aliased_import( def _analyze_top_level_function( self, function_name: str, type_name: Union[TypeAliasTopLevel, UserDefinedType] ) -> None: - for tl_function in self.compilation_unit.functions_top_level: - if tl_function.name == function_name: + for tl_function in self._using_for.file_scope.functions: + # The library function is bound to the first parameter's type + if ( + tl_function.name == function_name + and tl_function.parameters + and type_name == tl_function.parameters[0].type + ): self._using_for.using_for[type_name].append(tl_function) self._propagate_global(type_name) break @@ -108,7 +113,12 @@ def _analyze_library_function( break if c.name == library_name: for cf in c.functions: - if cf.name == function_name: + # The library function is bound to the first parameter's type + if ( + cf.name == function_name + and cf.parameters + and type_name == cf.parameters[0].type + ): self._using_for.using_for[type_name].append(cf) self._propagate_global(type_name) found = True @@ -132,7 +142,9 @@ def _propagate_global(self, type_name: Union[TypeAliasTopLevel, UserDefinedType] f"Error when propagating global using for {type_name} {type(type_name)}" ) - def _propagate_global_UserDefinedType(self, scope: FileScope, type_name: UserDefinedType): + def _propagate_global_UserDefinedType( + self, scope: FileScope, type_name: UserDefinedType + ) -> None: underlying = type_name.type if isinstance(underlying, StructureTopLevel): for struct in scope.structures.values(): diff --git a/slither/solc_parsing/expressions/expression_parsing.py b/slither/solc_parsing/expressions/expression_parsing.py index 1d839b92a8..29662059de 100644 --- a/slither/solc_parsing/expressions/expression_parsing.py +++ b/slither/solc_parsing/expressions/expression_parsing.py @@ -1,19 +1,12 @@ import logging import re -from typing import Dict, TYPE_CHECKING +from typing import Union, Dict, TYPE_CHECKING +import slither.core.expressions.type_conversion from slither.core.declarations.solidity_variables import ( SOLIDITY_VARIABLES_COMPOSED, SolidityVariableComposed, ) -from slither.core.expressions.assignment_operation import ( - AssignmentOperation, - AssignmentOperationType, -) -from slither.core.expressions.binary_operation import ( - BinaryOperation, - BinaryOperationType, -) from slither.core.expressions import ( CallExpression, ConditionalExpression, @@ -32,17 +25,30 @@ UnaryOperation, UnaryOperationType, ) +from slither.core.expressions.assignment_operation import ( + AssignmentOperation, + AssignmentOperationType, +) +from slither.core.expressions.binary_operation import ( + BinaryOperation, + BinaryOperationType, +) from slither.core.solidity_types import ( ArrayType, ElementaryType, + UserDefinedType, ) from slither.solc_parsing.declarations.caller_context import CallerContextExpression from slither.solc_parsing.exceptions import ParsingError, VariableNotFound from slither.solc_parsing.expressions.find_variable import find_variable from slither.solc_parsing.solidity_types.type_parsing import UnknownType, parse_type + if TYPE_CHECKING: from slither.core.expressions.expression import Expression + from slither.solc_parsing.declarations.contract import ContractSolc + from slither.solc_parsing.declarations.function import FunctionSolc + from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc logger = logging.getLogger("ExpressionParsing") @@ -97,8 +103,13 @@ def filter_name(value: str) -> str: ################################################################################### ################################################################################### - -def parse_call(expression: Dict, caller_context): # pylint: disable=too-many-statements +# pylint: disable=too-many-statements +def parse_call( + expression: Dict, caller_context: Union["FunctionSolc", "ContractSolc", "TopLevelVariableSolc"] +) -> Union[ + slither.core.expressions.call_expression.CallExpression, + slither.core.expressions.type_conversion.TypeConversion, +]: src = expression["src"] if caller_context.is_compact_ast: attributes = expression @@ -112,7 +123,6 @@ def parse_call(expression: Dict, caller_context): # pylint: disable=too-many-st if type_conversion: type_call = parse_type(UnknownType(type_return), caller_context) - if caller_context.is_compact_ast: assert len(expression["arguments"]) == 1 expression_to_parse = expression["arguments"][0] @@ -133,6 +143,8 @@ def parse_call(expression: Dict, caller_context): # pylint: disable=too-many-st expression = parse_expression(expression_to_parse, caller_context) t = TypeConversion(expression, type_call) t.set_offset(src, caller_context.compilation_unit) + if isinstance(type_call, UserDefinedType): + type_call.type.references.append(t.source_mapping) return t call_gas = None @@ -221,8 +233,7 @@ def _parse_elementary_type_name_expression( if TYPE_CHECKING: - - from slither.core.scope.scope import FileScope + pass def parse_expression(expression: Dict, caller_context: CallerContextExpression) -> "Expression": diff --git a/slither/solc_parsing/expressions/find_variable.py b/slither/solc_parsing/expressions/find_variable.py index 471f1a7507..32f5afc584 100644 --- a/slither/solc_parsing/expressions/find_variable.py +++ b/slither/solc_parsing/expressions/find_variable.py @@ -36,7 +36,7 @@ # CallerContext =Union["ContractSolc", "FunctionSolc", "CustomErrorSolc", "StructureTopLevelSolc"] -def _get_pointer_name(variable: Variable): +def _get_pointer_name(variable: Variable) -> Optional[str]: curr_type = variable.type while isinstance(curr_type, (ArrayType, MappingType)): if isinstance(curr_type, ArrayType): diff --git a/slither/solc_parsing/slither_compilation_unit_solc.py b/slither/solc_parsing/slither_compilation_unit_solc.py index e00c94689b..2cda7b2b17 100644 --- a/slither/solc_parsing/slither_compilation_unit_solc.py +++ b/slither/solc_parsing/slither_compilation_unit_solc.py @@ -19,6 +19,7 @@ from slither.core.solidity_types import ElementaryType, TypeAliasTopLevel from slither.core.variables.top_level_variable import TopLevelVariable from slither.exceptions import SlitherException +from slither.solc_parsing.declarations.caller_context import CallerContextExpression from slither.solc_parsing.declarations.contract import ContractSolc from slither.solc_parsing.declarations.custom_error import CustomErrorSolc from slither.solc_parsing.declarations.function import FunctionSolc @@ -26,7 +27,6 @@ from slither.solc_parsing.declarations.using_for_top_level import UsingForTopLevelSolc from slither.solc_parsing.exceptions import VariableNotFound from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc -from slither.solc_parsing.declarations.caller_context import CallerContextExpression logging.basicConfig() logger = logging.getLogger("SlitherSolcParsing") @@ -68,7 +68,7 @@ def _handle_import_aliases( class SlitherCompilationUnitSolc(CallerContextExpression): # pylint: disable=no-self-use,too-many-instance-attributes - def __init__(self, compilation_unit: SlitherCompilationUnit, generates_certik_ir: bool): + def __init__(self, compilation_unit: SlitherCompilationUnit, generates_certik_ir: bool) -> None: super().__init__() self._contracts_by_id: Dict[int, ContractSolc] = {} @@ -106,7 +106,7 @@ def generates_certik_ir(self) -> bool: def all_functions_and_modifiers_parser(self) -> List[FunctionSolc]: return self._all_functions_and_modifier_parser - def add_function_or_modifier_parser(self, f: FunctionSolc): + def add_function_or_modifier_parser(self, f: FunctionSolc) -> None: self._all_functions_and_modifier_parser.append(f) @property @@ -171,7 +171,7 @@ def parse_top_level_from_json(self, json_data: str) -> bool: return True return False - def _parse_enum(self, top_level_data: Dict, filename: str): + def _parse_enum(self, top_level_data: Dict, filename: str) -> None: if self.is_compact_ast: name = top_level_data["name"] canonicalName = top_level_data["canonicalName"] @@ -200,9 +200,8 @@ def _parse_enum(self, top_level_data: Dict, filename: str): enum.set_offset(top_level_data["src"], self._compilation_unit) self._compilation_unit.enums_top_level.append(enum) - def parse_top_level_from_loaded_json( - self, data_loaded: Dict, filename: str - ): # pylint: disable=too-many-branches,too-many-statements,too-many-locals + # pylint: disable=too-many-branches,too-many-statements,too-many-locals + def parse_top_level_from_loaded_json(self, data_loaded: Dict, filename: str) -> None: if "nodeType" in data_loaded: self._is_compact_ast = True @@ -350,7 +349,7 @@ def parse_top_level_from_loaded_json( else: raise SlitherException(f"Top level {top_level_data[self.get_key()]} not supported") - def _parse_source_unit(self, data: Dict, filename: str): + def _parse_source_unit(self, data: Dict, filename: str) -> None: if data[self.get_key()] != "SourceUnit": return # handle solc prior 0.3.6 @@ -400,7 +399,7 @@ def parsed(self) -> bool: def analyzed(self) -> bool: return self._analyzed - def parse_contracts(self): # pylint: disable=too-many-statements,too-many-branches + def parse_contracts(self) -> None: # pylint: disable=too-many-statements,too-many-branches if not self._underlying_contract_to_parser: logger.info( f"No contract were found in {self._compilation_unit.core.filename}, check the correct compilation" @@ -531,7 +530,7 @@ def parse_contracts(self): # pylint: disable=too-many-statements,too-many-branc self._parsed = True - def analyze_contracts(self): # pylint: disable=too-many-statements,too-many-branches + def analyze_contracts(self) -> None: # pylint: disable=too-many-statements,too-many-branches if not self._parsed: raise SlitherException("Parse the contract before running analyses") self._convert_to_slithir() @@ -540,7 +539,7 @@ def analyze_contracts(self): # pylint: disable=too-many-statements,too-many-bra self._compilation_unit.compute_storage_layout() self._analyzed = True - def _analyze_all_enums(self, contracts_to_be_analyzed: List[ContractSolc]): + def _analyze_all_enums(self, contracts_to_be_analyzed: List[ContractSolc]) -> None: while contracts_to_be_analyzed: contract = contracts_to_be_analyzed[0] @@ -559,7 +558,7 @@ def _analyze_first_part( self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc], - ): + ) -> None: for lib in libraries: self._parse_struct_var_modifiers_functions(lib) @@ -586,7 +585,7 @@ def _analyze_second_part( self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc], - ): + ) -> None: for lib in libraries: self._analyze_struct_events(lib) @@ -616,7 +615,7 @@ def _analyze_third_part( self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc], - ): + ) -> None: for lib in libraries: self._analyze_variables_modifiers_functions(lib) @@ -641,7 +640,7 @@ def _analyze_third_part( def _analyze_using_for( self, contracts_to_be_analyzed: List[ContractSolc], libraries: List[ContractSolc] - ): + ) -> None: self._analyze_top_level_using_for() for lib in libraries: @@ -662,12 +661,12 @@ def _analyze_using_for( else: contracts_to_be_analyzed += [contract] - def _analyze_enums(self, contract: ContractSolc): + def _analyze_enums(self, contract: ContractSolc) -> None: # Enum must be analyzed first contract.analyze_enums() contract.set_is_analyzed(True) - def _parse_struct_var_modifiers_functions(self, contract: ContractSolc): + def _parse_struct_var_modifiers_functions(self, contract: ContractSolc) -> None: contract.parse_structs() # struct can refer another struct contract.parse_state_variables() contract.parse_modifiers() @@ -675,7 +674,7 @@ def _parse_struct_var_modifiers_functions(self, contract: ContractSolc): contract.parse_custom_errors() contract.set_is_analyzed(True) - def _analyze_struct_events(self, contract: ContractSolc): + def _analyze_struct_events(self, contract: ContractSolc) -> None: contract.analyze_constant_state_variables() @@ -688,41 +687,41 @@ def _analyze_struct_events(self, contract: ContractSolc): contract.set_is_analyzed(True) - def _analyze_top_level_structures(self): + def _analyze_top_level_structures(self) -> None: try: for struct in self._structures_top_level_parser: struct.analyze() except (VariableNotFound, KeyError) as e: raise SlitherException(f"Missing struct {e} during top level structure analyze") from e - def _analyze_top_level_variables(self): + def _analyze_top_level_variables(self) -> None: try: for var in self._variables_top_level_parser: var.analyze(var) except (VariableNotFound, KeyError) as e: raise SlitherException(f"Missing {e} during variable analyze") from e - def _analyze_params_top_level_function(self): + def _analyze_params_top_level_function(self) -> None: for func_parser in self._functions_top_level_parser: func_parser.analyze_params() self._compilation_unit.add_function(func_parser.underlying_function) - def _analyze_top_level_using_for(self): + def _analyze_top_level_using_for(self) -> None: for using_for in self._using_for_top_level_parser: using_for.analyze() - def _analyze_params_custom_error(self): + def _analyze_params_custom_error(self) -> None: for custom_error_parser in self._custom_error_parser: custom_error_parser.analyze_params() - def _analyze_content_top_level_function(self): + def _analyze_content_top_level_function(self) -> None: try: for func_parser in self._functions_top_level_parser: func_parser.analyze_content() except (VariableNotFound, KeyError) as e: raise SlitherException(f"Missing {e} during top level function analyze") from e - def _analyze_variables_modifiers_functions(self, contract: ContractSolc): + def _analyze_variables_modifiers_functions(self, contract: ContractSolc) -> None: # State variables, modifiers and functions can refer to anything contract.analyze_params_modifiers() @@ -738,7 +737,7 @@ def _analyze_variables_modifiers_functions(self, contract: ContractSolc): contract.set_is_analyzed(True) - def _convert_to_slithir(self): + def _convert_to_slithir(self) -> None: for contract in self._compilation_unit.contracts: contract.add_constructor_variables() diff --git a/slither/solc_parsing/solidity_types/type_parsing.py b/slither/solc_parsing/solidity_types/type_parsing.py index 91e320a425..e12290722f 100644 --- a/slither/solc_parsing/solidity_types/type_parsing.py +++ b/slither/solc_parsing/solidity_types/type_parsing.py @@ -6,7 +6,7 @@ from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel from slither.core.declarations.function_contract import FunctionContract from slither.core.expressions.literal import Literal -from slither.core.solidity_types import TypeAlias +from slither.core.solidity_types import TypeAlias, TypeAliasTopLevel, TypeAliasContract from slither.core.solidity_types.array_type import ArrayType from slither.core.solidity_types.elementary_type import ( ElementaryType, @@ -22,7 +22,7 @@ from slither.solc_parsing.expressions.expression_parsing import CallerContextExpression if TYPE_CHECKING: - from slither.core.declarations import Structure, Enum + from slither.core.declarations import Structure, Enum, Function from slither.core.declarations.contract import Contract from slither.core.compilation_unit import SlitherCompilationUnit from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc @@ -33,11 +33,11 @@ class UnknownType: # pylint: disable=too-few-public-methods - def __init__(self, name): + def __init__(self, name: str) -> None: self._name = name @property - def name(self): + def name(self) -> str: return self._name @@ -195,10 +195,13 @@ def _find_from_type_name( # pylint: disable=too-many-locals,too-many-branches,t return UserDefinedType(var_type) -def _add_type_references(type_found: Type, src: str, sl: "SlitherCompilationUnit"): +def _add_type_references(type_found: Type, src: str, sl: "SlitherCompilationUnit") -> None: if isinstance(type_found, UserDefinedType): type_found.type.add_reference_from_raw_source(src, sl) + elif isinstance(type_found, (TypeAliasTopLevel, TypeAliasContract)): + type_found.type.add_reference_from_raw_source(src, sl) + type_found.add_reference_from_raw_source(src, sl) # TODO: since the add of FileScope, we can probably refactor this function and makes it a lot simpler @@ -233,6 +236,7 @@ def parse_type( sl: "SlitherCompilationUnit" renaming: Dict[str, str] user_defined_types: Dict[str, TypeAlias] + enums_direct_access: List["Enum"] = [] # Note: for convenicence top level functions use the same parser than function in contract # but contract_parser is set to None if isinstance(caller_context, SlitherCompilationUnitSolc) or ( @@ -254,7 +258,7 @@ def parse_type( all_structuress = [c.structures for c in sl.contracts] all_structures = [item for sublist in all_structuress for item in sublist] all_structures += structures_direct_access - enums_direct_access = sl.enums_top_level + enums_direct_access += sl.enums_top_level all_enumss = [c.enums for c in sl.contracts] all_enums = [item for sublist in all_enumss for item in sublist] all_enums += enums_direct_access @@ -316,7 +320,7 @@ def parse_type( all_structuress = [c.structures for c in contract.file_scope.contracts.values()] all_structures = [item for sublist in all_structuress for item in sublist] all_structures += contract.file_scope.structures.values() - enums_direct_access: List["Enum"] = contract.enums + enums_direct_access += contract.enums enums_direct_access += contract.file_scope.enums.values() all_enumss = [c.enums for c in contract.file_scope.contracts.values()] all_enums = [item for sublist in all_enumss for item in sublist] @@ -362,6 +366,7 @@ def parse_type( if name in renaming: name = renaming[name] if name in user_defined_types: + _add_type_references(user_defined_types[name], t["src"], sl) return user_defined_types[name] type_found = _find_from_type_name( name, @@ -382,6 +387,7 @@ def parse_type( if name in renaming: name = renaming[name] if name in user_defined_types: + _add_type_references(user_defined_types[name], t["src"], sl) return user_defined_types[name] type_found = _find_from_type_name( name, diff --git a/slither/solc_parsing/variables/event_variable.py b/slither/solc_parsing/variables/event_variable.py index 664b7d057d..fe30b8a3a2 100644 --- a/slither/solc_parsing/variables/event_variable.py +++ b/slither/solc_parsing/variables/event_variable.py @@ -14,7 +14,7 @@ def underlying_variable(self) -> EventVariable: assert isinstance(self._variable, EventVariable) return self._variable - def _analyze_variable_attributes(self, attributes: Dict): + def _analyze_variable_attributes(self, attributes: Dict) -> None: """ Analyze event variable attributes :param attributes: The event variable attributes to parse. diff --git a/slither/solc_parsing/variables/local_variable.py b/slither/solc_parsing/variables/local_variable.py index b9617a59c8..cd9030d58c 100644 --- a/slither/solc_parsing/variables/local_variable.py +++ b/slither/solc_parsing/variables/local_variable.py @@ -5,7 +5,7 @@ class LocalVariableSolc(VariableDeclarationSolc): - def __init__(self, variable: LocalVariable, variable_data: Dict): + def __init__(self, variable: LocalVariable, variable_data: Dict) -> None: super().__init__(variable, variable_data) @property @@ -14,7 +14,7 @@ def underlying_variable(self) -> LocalVariable: assert isinstance(self._variable, LocalVariable) return self._variable - def _analyze_variable_attributes(self, attributes: Dict): + def _analyze_variable_attributes(self, attributes: Dict) -> None: """' Variable Location Can be storage/memory or default diff --git a/slither/solc_parsing/variables/local_variable_init_from_tuple.py b/slither/solc_parsing/variables/local_variable_init_from_tuple.py index 72c57281e3..1a551c6957 100644 --- a/slither/solc_parsing/variables/local_variable_init_from_tuple.py +++ b/slither/solc_parsing/variables/local_variable_init_from_tuple.py @@ -5,7 +5,9 @@ class LocalVariableInitFromTupleSolc(VariableDeclarationSolc): - def __init__(self, variable: LocalVariableInitFromTuple, variable_data: Dict, index: int): + def __init__( + self, variable: LocalVariableInitFromTuple, variable_data: Dict, index: int + ) -> None: super().__init__(variable, variable_data) variable.tuple_index = index diff --git a/slither/solc_parsing/variables/state_variable.py b/slither/solc_parsing/variables/state_variable.py index 3fa1320770..a9c0ff730b 100644 --- a/slither/solc_parsing/variables/state_variable.py +++ b/slither/solc_parsing/variables/state_variable.py @@ -5,7 +5,7 @@ class StateVariableSolc(VariableDeclarationSolc): - def __init__(self, variable: StateVariable, variable_data: Dict): + def __init__(self, variable: StateVariable, variable_data: Dict) -> None: super().__init__(variable, variable_data) @property diff --git a/slither/solc_parsing/variables/top_level_variable.py b/slither/solc_parsing/variables/top_level_variable.py index 6c24c3bdf7..56eb79c463 100644 --- a/slither/solc_parsing/variables/top_level_variable.py +++ b/slither/solc_parsing/variables/top_level_variable.py @@ -15,7 +15,7 @@ def __init__( variable: TopLevelVariable, variable_data: Dict, slither_parser: "SlitherCompilationUnitSolc", - ): + ) -> None: super().__init__(variable, variable_data) self._slither_parser = slither_parser diff --git a/slither/solc_parsing/variables/variable_declaration.py b/slither/solc_parsing/variables/variable_declaration.py index 7385d29a7f..3d74a91964 100644 --- a/slither/solc_parsing/variables/variable_declaration.py +++ b/slither/solc_parsing/variables/variable_declaration.py @@ -1,7 +1,7 @@ import copy import logging import re -from typing import Dict +from typing import Dict, Optional from slither.solc_parsing.declarations.caller_context import CallerContextExpression from slither.solc_parsing.expressions.expression_parsing import parse_expression @@ -50,9 +50,8 @@ class MultipleVariablesDeclaration(Exception): class VariableDeclarationSolc: - def __init__( - self, variable: Variable, variable_data: Dict - ): # pylint: disable=too-many-branches + # pylint: disable=too-many-branches + def __init__(self, variable: Variable, variable_data: Dict) -> None: """ A variable can be declared through a statement, or directly. If it is through a statement, the following children may contain @@ -123,7 +122,7 @@ def reference_id(self) -> int: """ return self._reference_id - def _handle_comment(self, attributes: Dict): + def _handle_comment(self, attributes: Dict) -> None: if "documentation" in attributes and "text" in attributes["documentation"]: candidates = attributes["documentation"]["text"].split(",") @@ -140,13 +139,15 @@ def _handle_comment(self, attributes: Dict): self._variable.write_protection = [] self._variable.write_protection.append(write_protection.group(1)) - def _analyze_variable_attributes(self, attributes: Dict): + def _analyze_variable_attributes(self, attributes: Dict) -> None: if "visibility" in attributes: self._variable.visibility = attributes["visibility"] else: self._variable.visibility = "internal" - def _init_from_declaration(self, var: Dict, init: bool): # pylint: disable=too-many-branches + def _init_from_declaration( + self, var: Dict, init: Optional[bool] + ) -> None: # pylint: disable=too-many-branches if self._is_compact_ast: attributes = var self._typeName = attributes["typeDescriptions"]["typeString"] @@ -220,7 +221,7 @@ def _init_from_declaration(self, var: Dict, init: bool): # pylint: disable=too- self._variable.initialized = True self._initializedNotParsed = var["children"][1] - def analyze(self, caller_context: CallerContextExpression): + def analyze(self, caller_context: CallerContextExpression) -> None: # Can be re-analyzed due to inheritance if self._was_analyzed: return diff --git a/slither/solc_parsing/yul/evm_functions.py b/slither/solc_parsing/yul/evm_functions.py index 41c1507659..dfb52a2448 100644 --- a/slither/solc_parsing/yul/evm_functions.py +++ b/slither/solc_parsing/yul/evm_functions.py @@ -225,7 +225,7 @@ } -def format_function_descriptor(name): +def format_function_descriptor(name: str) -> str: if name not in function_args: return name + "()" diff --git a/slither/solc_parsing/yul/parse_yul.py b/slither/solc_parsing/yul/parse_yul.py index f7c9938fc4..35d5cdd9d0 100644 --- a/slither/solc_parsing/yul/parse_yul.py +++ b/slither/solc_parsing/yul/parse_yul.py @@ -43,7 +43,7 @@ class YulNode: - def __init__(self, node: Node, scope: "YulScope"): + def __init__(self, node: Node, scope: "YulScope") -> None: self._node = node self._scope = scope self._unparsed_expression: Optional[Dict] = None @@ -99,7 +99,7 @@ def analyze_expressions(self) -> None: ] -def link_underlying_nodes(node1: YulNode, node2: YulNode): +def link_underlying_nodes(node1: YulNode, node2: YulNode) -> None: link_nodes(node1.underlying_node, node2.underlying_node) @@ -191,7 +191,7 @@ def get_yul_local_function_from_name(self, func_name: str) -> Optional["YulLocal class YulLocalVariable: # pylint: disable=too-few-public-methods __slots__ = ["_variable", "_root"] - def __init__(self, var: LocalVariable, root: YulScope, ast: Dict): + def __init__(self, var: LocalVariable, root: YulScope, ast: Dict) -> None: assert ast["nodeType"] == "YulTypedName" self._variable = var @@ -215,7 +215,7 @@ class YulFunction(YulScope): def __init__( self, func: Function, root: YulScope, ast: Dict, node_scope: Union[Function, Scope] - ): + ) -> None: super().__init__(root.contract, root.id + [ast["name"]], parent_func=root.parent_func) assert ast["nodeType"] == "YulFunctionDefinition" @@ -272,7 +272,7 @@ def parse_body(self) -> None: for node in self._nodes: node.analyze_expressions() - def new_node(self, node_type, src) -> YulNode: + def new_node(self, node_type: NodeType, src: str) -> YulNode: if self._function: node = self._function.new_node(node_type, src, self.node_scope) else: @@ -299,7 +299,7 @@ def __init__( entrypoint: Node, yul_id: List[str], node_scope: Union[Scope, Function], - ): + ) -> None: super().__init__(contract, yul_id, entrypoint.function) self._entrypoint: YulNode = YulNode(entrypoint, self) @@ -792,8 +792,9 @@ def parse_yul_identifier(root: YulScope, _node: YulNode, ast: Dict) -> Optional[ return Identifier(local_variable) if isinstance(parent_func, FunctionContract): - assert parent_func.contract - state_variable = parent_func.contract.get_state_variable_from_name(name) + # Variables must be looked from the contract declarer + assert parent_func.contract_declarer + state_variable = parent_func.contract_declarer.get_state_variable_from_name(name) if state_variable: return Identifier(state_variable) @@ -883,7 +884,9 @@ def vars_to_typestr(rets: List[Expression]) -> str: return f"tuple({','.join(str(ret.type) for ret in rets)})" -def vars_to_val(vars_to_convert): +def vars_to_val( + vars_to_convert: List[Identifier], +) -> Identifier: if len(vars_to_convert) == 1: return vars_to_convert[0] return TupleExpression(vars_to_convert) diff --git a/slither/tools/doctor/__main__.py b/slither/tools/doctor/__main__.py index b9b4c54977..f401781a77 100644 --- a/slither/tools/doctor/__main__.py +++ b/slither/tools/doctor/__main__.py @@ -26,7 +26,7 @@ def parse_args() -> argparse.Namespace: return parser.parse_args() -def main(): +def main() -> None: # log on stdout to keep output in order logging.basicConfig(stream=sys.stdout, level=logging.DEBUG, force=True) diff --git a/slither/tools/documentation/__main__.py b/slither/tools/documentation/__main__.py index 8e545fb095..39b1eacdac 100644 --- a/slither/tools/documentation/__main__.py +++ b/slither/tools/documentation/__main__.py @@ -36,6 +36,13 @@ def parse_args() -> argparse.Namespace: default=False, ) + parser.add_argument( + "--include-tests", + help="Include the tests", + action="store_true", + default=False, + ) + parser.add_argument( "--retry", help="Retry failed query (default 1). Each retry increases the temperature by 0.1", @@ -202,6 +209,7 @@ def _handle_compilation_unit( overwrite: bool, force: bool, retry: int, + include_test: bool, ) -> None: logging_file: Optional[str] if slither.codex_log: @@ -210,9 +218,8 @@ def _handle_compilation_unit( logging_file = None for scope in compilation_unit.scopes.values(): - # Dont send tests file - if ( + if not include_test and ( ".t.sol" in scope.filename.absolute or "mock" in scope.filename.absolute.lower() or "test" in scope.filename.absolute.lower() @@ -271,6 +278,7 @@ def main() -> None: args.overwrite, args.force_answer_parsing, int(args.retry), + args.include_tests, ) except ImportError: pass diff --git a/slither/tools/erc_conformance/__main__.py b/slither/tools/erc_conformance/__main__.py index ef594a7c66..1c9224eacb 100644 --- a/slither/tools/erc_conformance/__main__.py +++ b/slither/tools/erc_conformance/__main__.py @@ -1,15 +1,17 @@ import argparse import logging from collections import defaultdict -from typing import Any, Dict, List +from typing import Any, Dict, List, Callable from crytic_compile import cryticparser + from slither import Slither +from slither.core.declarations import Contract from slither.utils.erc import ERCS from slither.utils.output import output_to_json -from .erc.ercs import generic_erc_checks -from .erc.erc20 import check_erc20 from .erc.erc1155 import check_erc1155 +from .erc.erc20 import check_erc20 +from .erc.ercs import generic_erc_checks logging.basicConfig() logging.getLogger("Slither").setLevel(logging.INFO) @@ -24,7 +26,10 @@ logger.handlers[0].setFormatter(formatter) logger.propagate = False -ADDITIONAL_CHECKS = {"ERC20": check_erc20, "ERC1155": check_erc1155} +ADDITIONAL_CHECKS: Dict[str, Callable[[Contract, Dict[str, List]], Dict[str, List]]] = { + "ERC20": check_erc20, + "ERC1155": check_erc1155, +} def parse_args() -> argparse.Namespace: diff --git a/slither/tools/erc_conformance/erc/erc1155.py b/slither/tools/erc_conformance/erc/erc1155.py index fceb4e2422..34bb18bfbb 100644 --- a/slither/tools/erc_conformance/erc/erc1155.py +++ b/slither/tools/erc_conformance/erc/erc1155.py @@ -1,12 +1,14 @@ import logging +from typing import Dict, List, Optional +from slither.core.declarations import Contract from slither.slithir.operations import EventCall from slither.utils import output logger = logging.getLogger("Slither-conformance") -def events_safeBatchTransferFrom(contract, ret): +def events_safeBatchTransferFrom(contract: Contract, ret: Dict[str, List]) -> None: function = contract.get_function_from_signature( "safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)" ) @@ -44,7 +46,9 @@ def events_safeBatchTransferFrom(contract, ret): ) -def check_erc1155(contract, ret, explored=None): +def check_erc1155( + contract: Contract, ret: Dict[str, List], explored: Optional[bool] = None +) -> Dict[str, List]: if explored is None: explored = set() diff --git a/slither/tools/erc_conformance/erc/erc20.py b/slither/tools/erc_conformance/erc/erc20.py index 720b08322e..6ee2435152 100644 --- a/slither/tools/erc_conformance/erc/erc20.py +++ b/slither/tools/erc_conformance/erc/erc20.py @@ -1,11 +1,13 @@ import logging +from typing import Dict, List, Optional +from slither.core.declarations import Contract from slither.utils import output logger = logging.getLogger("Slither-conformance") -def approval_race_condition(contract, ret): +def approval_race_condition(contract: Contract, ret: Dict[str, List]) -> None: increaseAllowance = contract.get_function_from_signature("increaseAllowance(address,uint256)") if not increaseAllowance: @@ -27,7 +29,9 @@ def approval_race_condition(contract, ret): ) -def check_erc20(contract, ret, explored=None): +def check_erc20( + contract: Contract, ret: Dict[str, List], explored: Optional[bool] = None +) -> Dict[str, List]: if explored is None: explored = set() diff --git a/slither/tools/flattening/flattening.py b/slither/tools/flattening/flattening.py index 74a93ba2da..67b3c00a36 100644 --- a/slither/tools/flattening/flattening.py +++ b/slither/tools/flattening/flattening.py @@ -80,11 +80,7 @@ def __init__( def _get_source_code_top_level(self, elems: Sequence[TopLevel]) -> None: for elem in elems: - src_mapping = elem.source_mapping - content = self._compilation_unit.core.source_code[src_mapping.filename.absolute] - start = src_mapping.start - end = src_mapping.start + src_mapping.length - self._source_codes_top_level[elem] = content[start:end] + self._source_codes_top_level[elem] = elem.source_mapping.content def _check_abi_encoder_v2(self): """ diff --git a/slither/tools/kspec_coverage/analysis.py b/slither/tools/kspec_coverage/analysis.py index 3d513d22fc..763939e251 100755 --- a/slither/tools/kspec_coverage/analysis.py +++ b/slither/tools/kspec_coverage/analysis.py @@ -1,11 +1,13 @@ -import re import logging -from typing import Set, Tuple +import re +from argparse import Namespace +from typing import Set, Tuple, List, Dict, Union, Optional, Callable -from slither.core.declarations import Function -from slither.core.variables.variable import Variable -from slither.utils.colors import yellow, green, red +from slither.core.compilation_unit import SlitherCompilationUnit +from slither.core.declarations import FunctionContract +from slither.core.variables.state_variable import StateVariable from slither.utils import output +from slither.utils.colors import yellow, green, red logging.basicConfig(level=logging.WARNING) logger = logging.getLogger("Slither.kspec") @@ -54,13 +56,15 @@ def _get_all_covered_kspec_functions(target: str) -> Set[Tuple[str, str]]: return covered_functions -def _get_slither_functions(slither): +def _get_slither_functions( + slither: SlitherCompilationUnit, +) -> Dict[Tuple[str, str], Union[FunctionContract, StateVariable]]: # Use contract == contract_declarer to avoid dupplicate - all_functions_declared = [ + all_functions_declared: List[Union[FunctionContract, StateVariable]] = [ f for f in slither.functions if ( - f.contract == f.contract_declarer + (isinstance(f, FunctionContract) and f.contract == f.contract_declarer) and f.is_implemented and not f.is_constructor and not f.is_constructor_variables @@ -79,7 +83,12 @@ def _get_slither_functions(slither): return slither_functions -def _generate_output(kspec, message, color, generate_json): +def _generate_output( + kspec: List[Union[FunctionContract, StateVariable]], + message: str, + color: Callable[[str], str], + generate_json: bool, +) -> Optional[Dict]: info = "" for function in kspec: info += f"{message} {function.contract.name}.{function.full_name}\n" @@ -94,7 +103,9 @@ def _generate_output(kspec, message, color, generate_json): return None -def _generate_output_unresolved(kspec, message, color, generate_json): +def _generate_output_unresolved( + kspec: Set[Tuple[str, str]], message: str, color: Callable[[str], str], generate_json: bool +) -> Optional[Dict]: info = "" for contract, function in kspec: info += f"{message} {contract}.{function}\n" @@ -107,17 +118,19 @@ def _generate_output_unresolved(kspec, message, color, generate_json): return None -def _run_coverage_analysis(args, slither, kspec_functions): +def _run_coverage_analysis( + args: Namespace, slither: SlitherCompilationUnit, kspec_functions: Set[Tuple[str, str]] +) -> None: # Collect all slither functions slither_functions = _get_slither_functions(slither) # Determine which klab specs were not resolved. slither_functions_set = set(slither_functions) kspec_functions_resolved = kspec_functions & slither_functions_set - kspec_functions_unresolved = kspec_functions - kspec_functions_resolved + kspec_functions_unresolved: Set[Tuple[str, str]] = kspec_functions - kspec_functions_resolved - kspec_missing = [] - kspec_present = [] + kspec_missing: List[Union[FunctionContract, StateVariable]] = [] + kspec_present: List[Union[FunctionContract, StateVariable]] = [] for slither_func_desc in sorted(slither_functions_set): slither_func = slither_functions[slither_func_desc] @@ -130,13 +143,13 @@ def _run_coverage_analysis(args, slither, kspec_functions): logger.info("## Check for functions coverage") json_kspec_present = _generate_output(kspec_present, "[✓]", green, args.json) json_kspec_missing_functions = _generate_output( - [f for f in kspec_missing if isinstance(f, Function)], + [f for f in kspec_missing if isinstance(f, FunctionContract)], "[ ] (Missing function)", red, args.json, ) json_kspec_missing_variables = _generate_output( - [f for f in kspec_missing if isinstance(f, Variable)], + [f for f in kspec_missing if isinstance(f, StateVariable)], "[ ] (Missing variable)", yellow, args.json, @@ -159,11 +172,11 @@ def _run_coverage_analysis(args, slither, kspec_functions): ) -def run_analysis(args, slither, kspec_arg): +def run_analysis(args: Namespace, slither: SlitherCompilationUnit, kspec_arg: str) -> None: # Get all of our kspec'd functions (tuple(contract_name, function_name)). if "," in kspec_arg: kspecs = kspec_arg.split(",") - kspec_functions = set() + kspec_functions: Set[Tuple[str, str]] = set() for kspec in kspecs: kspec_functions |= _get_all_covered_kspec_functions(kspec) else: diff --git a/slither/tools/mutator/__main__.py b/slither/tools/mutator/__main__.py index 78b86d681a..27e396d0b1 100644 --- a/slither/tools/mutator/__main__.py +++ b/slither/tools/mutator/__main__.py @@ -72,7 +72,7 @@ def __call__( ################################################################################### -def main(): +def main() -> None: args = parse_args() diff --git a/slither/tools/mutator/mutators/MIA.py b/slither/tools/mutator/mutators/MIA.py index 54ca0ec1ca..405888f8bf 100644 --- a/slither/tools/mutator/mutators/MIA.py +++ b/slither/tools/mutator/mutators/MIA.py @@ -1,3 +1,5 @@ +from typing import Dict + from slither.core.cfg.node import NodeType from slither.formatters.utils.patches import create_patch from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator, FaultNature, FaultClass @@ -9,13 +11,13 @@ class MIA(AbstractMutator): # pylint: disable=too-few-public-methods FAULTCLASS = FaultClass.Checking FAULTNATURE = FaultNature.Missing - def _mutate(self): + def _mutate(self) -> Dict: - result = {} + result: Dict = {} for contract in self.slither.contracts: - for function in contract.functions_declared + contract.modifiers_declared: + for function in contract.functions_declared + list(contract.modifiers_declared): for node in function.nodes: if node.type == NodeType.IF: diff --git a/slither/tools/mutator/mutators/MVIE.py b/slither/tools/mutator/mutators/MVIE.py index 8f8cc11bf7..a16a8252e2 100644 --- a/slither/tools/mutator/mutators/MVIE.py +++ b/slither/tools/mutator/mutators/MVIE.py @@ -1,4 +1,7 @@ +from typing import Dict + from slither.core.expressions import Literal +from slither.core.variables.variable import Variable from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator, FaultNature, FaultClass from slither.tools.mutator.utils.generic_patching import remove_assignement @@ -9,10 +12,10 @@ class MVIE(AbstractMutator): # pylint: disable=too-few-public-methods FAULTCLASS = FaultClass.Assignement FAULTNATURE = FaultNature.Missing - def _mutate(self): - - result = {} + def _mutate(self) -> Dict: + result: Dict = {} + variable: Variable for contract in self.slither.contracts: # Create fault for state variables declaration @@ -25,7 +28,7 @@ def _mutate(self): if not isinstance(variable.expression, Literal): remove_assignement(variable, contract, result) - for function in contract.functions_declared + contract.modifiers_declared: + for function in contract.functions_declared + list(contract.modifiers_declared): for variable in function.local_variables: if variable.initialized and not isinstance(variable.expression, Literal): remove_assignement(variable, contract, result) diff --git a/slither/tools/mutator/mutators/MVIV.py b/slither/tools/mutator/mutators/MVIV.py index dac34da283..d4a7c54868 100644 --- a/slither/tools/mutator/mutators/MVIV.py +++ b/slither/tools/mutator/mutators/MVIV.py @@ -1,4 +1,7 @@ +from typing import Dict + from slither.core.expressions import Literal +from slither.core.variables.variable import Variable from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator, FaultNature, FaultClass from slither.tools.mutator.utils.generic_patching import remove_assignement @@ -9,9 +12,10 @@ class MVIV(AbstractMutator): # pylint: disable=too-few-public-methods FAULTCLASS = FaultClass.Assignement FAULTNATURE = FaultNature.Missing - def _mutate(self): + def _mutate(self) -> Dict: - result = {} + result: Dict = {} + variable: Variable for contract in self.slither.contracts: @@ -25,7 +29,7 @@ def _mutate(self): if isinstance(variable.expression, Literal): remove_assignement(variable, contract, result) - for function in contract.functions_declared + contract.modifiers_declared: + for function in contract.functions_declared + list(contract.modifiers_declared): for variable in function.local_variables: if variable.initialized and isinstance(variable.expression, Literal): remove_assignement(variable, contract, result) diff --git a/slither/tools/mutator/utils/command_line.py b/slither/tools/mutator/utils/command_line.py index 9799fd488e..840976ccfc 100644 --- a/slither/tools/mutator/utils/command_line.py +++ b/slither/tools/mutator/utils/command_line.py @@ -1,7 +1,10 @@ +from typing import List, Type + +from slither.tools.mutator.mutators.abstract_mutator import AbstractMutator from slither.utils.myprettytable import MyPrettyTable -def output_mutators(mutators_classes): +def output_mutators(mutators_classes: List[Type[AbstractMutator]]) -> None: mutators_list = [] for detector in mutators_classes: argument = detector.NAME diff --git a/slither/tools/read_storage/read_storage.py b/slither/tools/read_storage/read_storage.py index bb662c4d5e..387aa619a2 100644 --- a/slither/tools/read_storage/read_storage.py +++ b/slither/tools/read_storage/read_storage.py @@ -1,30 +1,22 @@ import logging -import sys from math import floor -from typing import Callable, Optional, Tuple, Union, List, Dict, Any - -try: - from web3 import Web3 - from eth_typing.evm import ChecksumAddress - from eth_abi import decode_single, encode_abi - from eth_utils import keccak - from .utils import ( - get_offset_value, - get_storage_data, - coerce_type, - ) -except ImportError: - print("ERROR: in order to use slither-read-storage, you need to install web3") - print("$ pip3 install web3 --user\n") - sys.exit(-1) +from typing import Any, Callable, Dict, List, Optional, Tuple, Union import dataclasses -from slither.utils.myprettytable import MyPrettyTable -from slither.core.solidity_types.type import Type -from slither.core.solidity_types import ArrayType, ElementaryType, UserDefinedType, MappingType + +from eth_abi import decode, encode +from eth_typing.evm import ChecksumAddress +from eth_utils import keccak +from web3 import Web3 + from slither.core.declarations import Contract, Structure +from slither.core.solidity_types import ArrayType, ElementaryType, MappingType, UserDefinedType +from slither.core.solidity_types.type import Type from slither.core.variables.state_variable import StateVariable from slither.core.variables.structure_variable import StructureVariable +from slither.utils.myprettytable import MyPrettyTable + +from .utils import coerce_type, get_offset_value, get_storage_data logging.basicConfig() logger = logging.getLogger("Slither-read-storage") @@ -92,7 +84,7 @@ def checksum_address(self) -> ChecksumAddress: if not self.storage_address: raise ValueError if not self._checksum_address: - self._checksum_address = self.web3.toChecksumAddress(self.storage_address) + self._checksum_address = self.web3.to_checksum_address(self.storage_address) return self._checksum_address @property @@ -449,7 +441,7 @@ def _find_mapping_slot( if "int" in key_type: # without this eth_utils encoding fails key = int(key) key = coerce_type(key_type, key) - slot = keccak(encode_abi([key_type, "uint256"], [key, decode_single("uint256", slot)])) + slot = keccak(encode([key_type, "uint256"], [key, decode("uint256", slot)])) if isinstance(target_variable_type.type_to, UserDefinedType) and isinstance( target_variable_type.type_to.type, Structure @@ -471,7 +463,7 @@ def _find_mapping_slot( deep_key = int(deep_key) # If deep map, will be keccak256(abi.encode(key1, keccak256(abi.encode(key0, uint(slot))))) - slot = keccak(encode_abi([key_type, "bytes32"], [deep_key, slot])) + slot = keccak(encode([key_type, "bytes32"], [deep_key, slot])) # mapping(elem => mapping(elem => elem)) target_variable_type_type_to_type_to = target_variable_type.type_to.type_to diff --git a/slither/tools/read_storage/utils/__init__.py b/slither/tools/read_storage/utils/__init__.py index 2fb43c8b87..9a624a4c72 100644 --- a/slither/tools/read_storage/utils/__init__.py +++ b/slither/tools/read_storage/utils/__init__.py @@ -1,5 +1 @@ -from .utils import ( - get_offset_value, - get_storage_data, - coerce_type, -) +from .utils import coerce_type, get_offset_value, get_storage_data diff --git a/slither/tools/read_storage/utils/utils.py b/slither/tools/read_storage/utils/utils.py index 3e51e21813..befd3d0e79 100644 --- a/slither/tools/read_storage/utils/utils.py +++ b/slither/tools/read_storage/utils/utils.py @@ -1,7 +1,7 @@ from typing import Union from eth_typing.evm import ChecksumAddress -from eth_utils import to_int, to_text, to_checksum_address +from eth_utils import to_checksum_address, to_int, to_text def get_offset_value(hex_bytes: bytes, offset: int, size: int) -> bytes: diff --git a/slither/tools/similarity/encode.py b/slither/tools/similarity/encode.py index d08086282a..48700ec4ad 100644 --- a/slither/tools/similarity/encode.py +++ b/slither/tools/similarity/encode.py @@ -1,5 +1,6 @@ import logging import os +from typing import Optional, Tuple, List from slither import Slither from slither.core.declarations import ( @@ -60,7 +61,7 @@ slither_logger.setLevel(logging.CRITICAL) -def parse_target(target): +def parse_target(target: Optional[str]) -> Tuple[Optional[str], Optional[str]]: if target is None: return None, None @@ -68,9 +69,9 @@ def parse_target(target): if len(parts) == 1: return None, parts[0] if len(parts) == 2: - return parts + return parts[0], parts[1] simil_logger.error("Invalid target. It should be 'function' or 'Contract.function'") - return None + return None, None def load_and_encode(infile: str, vmodel, ext=None, nsamples=None, **kwargs): @@ -88,7 +89,9 @@ def load_and_encode(infile: str, vmodel, ext=None, nsamples=None, **kwargs): return r -def load_contracts(dirname, ext=None, nsamples=None): +def load_contracts( + dirname: str, ext: Optional[str] = None, nsamples: Optional[int] = None +) -> List[str]: r = [] walk = list(os.walk(dirname)) for x, y, files in walk: diff --git a/slither/tools/similarity/test.py b/slither/tools/similarity/test.py index 76229d5bf1..7d42c4a63f 100755 --- a/slither/tools/similarity/test.py +++ b/slither/tools/similarity/test.py @@ -2,6 +2,7 @@ import operator import sys import traceback +from argparse import Namespace from slither.tools.similarity.encode import encode_contract, load_and_encode, parse_target from slither.tools.similarity.model import load_model @@ -10,7 +11,7 @@ logger = logging.getLogger("Slither-simil") -def test(args): +def test(args: Namespace) -> None: try: model = args.model diff --git a/slither/utils/arithmetic.py b/slither/utils/arithmetic.py index 18ffaec51a..0296231afe 100644 --- a/slither/utils/arithmetic.py +++ b/slither/utils/arithmetic.py @@ -1,6 +1,12 @@ +from typing import List, TYPE_CHECKING + from slither.exceptions import SlitherException from slither.utils.integer_conversion import convert_string_to_fraction + +if TYPE_CHECKING: + from slither.core.declarations import Contract, Function + # pylint: disable=too-many-branches def convert_subdenomination( value: str, sub: str @@ -31,3 +37,52 @@ def convert_subdenomination( return int(decimal_value * 60 * 60 * 24 * 7 * 365) raise SlitherException(f"Subdemonination conversion impossible {decimal_value} {sub}") + + +# Number of unchecked arithmetic operation needed to be interesting +THRESHOLD_ARITHMETIC_USAGE = 3 + + +def _unchecked_arithemtic_usage(function: "Function") -> bool: + """ + Check if the function has more than THRESHOLD_ARITHMETIC_USAGE unchecked arithmetic operation + + Args: + function: + + Returns: + + """ + + # pylint: disable=import-outside-toplevel + from slither.slithir.operations import Binary + + score = 0 + for node in function.nodes: + if not node.scope.is_checked: + for ir in node.irs: + if isinstance(ir, Binary): + score += 1 + if score >= THRESHOLD_ARITHMETIC_USAGE: + return True + return False + + +def unchecked_arithemtic_usage(contract: "Contract") -> List["Function"]: + """ + Return the list of function with some unchecked arithmetics + + Args: + contract: + + Returns: + + """ + # pylint: disable=import-outside-toplevel + from slither.core.declarations import Function + + ret: List[Function] = [] + for function in contract.all_functions_called: + if isinstance(function, Function) and _unchecked_arithemtic_usage(function): + ret.append(function) + return ret diff --git a/slither/utils/code_complexity.py b/slither/utils/code_complexity.py index 54b14f028d..a389663b33 100644 --- a/slither/utils/code_complexity.py +++ b/slither/utils/code_complexity.py @@ -52,7 +52,7 @@ def assign(node: "Node", root: List["Node"]): for father in node.fathers: assign(father, root) - for n in l: + for n in reversed(l): component: List["Node"] = [] assign(n, component) if component: @@ -74,9 +74,9 @@ def compute_cyclomatic_complexity(function: "Function") -> int: # where M is the complexity # E number of edges # N number of nodes - # P number of connected components + # P number of connected components (always 1 for a function) E = compute_number_edges(function) N = len(function.nodes) - P = len(compute_strongly_connected_components(function)) + P = 1 return E - N + 2 * P diff --git a/slither/utils/codex.py b/slither/utils/codex.py index 3b06efe6f5..8b15656708 100644 --- a/slither/utils/codex.py +++ b/slither/utils/codex.py @@ -64,6 +64,13 @@ def init_parser(parser: ArgumentParser, always_enable_codex: bool = False) -> No default=defaults_flag_in_config["codex_max_tokens"], ) + group_codex.add_argument( + "--codex-organization", + help="Codex organization", + action="store", + default=None, + ) + # TODO: investigate how to set the correct return type # So that the other modules can work with openai diff --git a/slither/utils/command_line.py b/slither/utils/command_line.py index 174c2a4b62..911609bf6d 100644 --- a/slither/utils/command_line.py +++ b/slither/utils/command_line.py @@ -169,7 +169,9 @@ def convert_result_to_markdown(txt: str) -> str: return "".join(ret) -def output_results_to_markdown(all_results: List[Dict], checklistlimit: str) -> None: +def output_results_to_markdown( + all_results: List[Dict], checklistlimit: str, show_ignored_findings: bool +) -> None: checks = defaultdict(list) info: Dict = defaultdict(dict) for results_ in all_results: @@ -179,6 +181,11 @@ def output_results_to_markdown(all_results: List[Dict], checklistlimit: str) -> "confidence": results_["confidence"], } + if not show_ignored_findings: + print( + "**THIS CHECKLIST IS NOT COMPLETE**. Use `--show-ignored-findings` to show all the results." + ) + print("Summary") for check_ in checks: print( diff --git a/slither/utils/comparable_enum.py b/slither/utils/comparable_enum.py index 63e476d6a1..d6f06bbbdc 100644 --- a/slither/utils/comparable_enum.py +++ b/slither/utils/comparable_enum.py @@ -1,26 +1,27 @@ from enum import Enum # pylint: disable=comparison-with-callable +from typing import Any class ComparableEnum(Enum): - def __eq__(self, other): + def __eq__(self, other: Any) -> bool: if isinstance(other, ComparableEnum): return self.value == other.value return False - def __ne__(self, other): + def __ne__(self, other: Any) -> bool: if isinstance(other, ComparableEnum): return self.value != other.value return False - def __lt__(self, other): + def __lt__(self, other: Any) -> bool: if isinstance(other, ComparableEnum): return self.value < other.value return False - def __repr__(self): + def __repr__(self) -> str: return f"{str(self.value)}" - def __hash__(self): + def __hash__(self) -> int: return hash(self.value) diff --git a/slither/utils/expression_manipulations.py b/slither/utils/expression_manipulations.py index a63db98293..753778be9a 100644 --- a/slither/utils/expression_manipulations.py +++ b/slither/utils/expression_manipulations.py @@ -4,6 +4,8 @@ """ import copy from typing import Union, Callable + +from slither.all_exceptions import SlitherException from slither.core.expressions import UnaryOperation from slither.core.expressions.assignment_operation import AssignmentOperation from slither.core.expressions.binary_operation import BinaryOperation @@ -19,7 +21,7 @@ from slither.core.expressions.new_contract import NewContract from slither.core.expressions.tuple_expression import TupleExpression from slither.core.expressions.type_conversion import TypeConversion -from slither.all_exceptions import SlitherException + # pylint: disable=protected-access def f_expressions( @@ -29,7 +31,7 @@ def f_expressions( e._expressions.append(x) -def f_call(e: CallExpression, x): +def f_call(e: CallExpression, x: ElementaryTypeNameExpression) -> None: e._arguments.append(x) @@ -41,11 +43,11 @@ def f_call_gas(e: CallExpression, x): e._gas = x -def f_expression(e: Union[TypeConversion, UnaryOperation, MemberAccess], x): +def f_expression(e: Union[TypeConversion, UnaryOperation, MemberAccess], x: CallExpression) -> None: e._expression = x -def f_called(e: CallExpression, x): +def f_called(e: CallExpression, x: Identifier) -> None: e._called = x @@ -98,11 +100,13 @@ def copy_expression( if isinstance( expression, - (Literal, Identifier, IndexAccess, NewArray, NewContract, ElementaryTypeNameExpression), + (Literal, Identifier, NewArray, NewContract, ElementaryTypeNameExpression), ): return - if isinstance(expression, (AssignmentOperation, BinaryOperation, TupleExpression)): + if isinstance( + expression, (AssignmentOperation, BinaryOperation, TupleExpression, IndexAccess) + ): true_expression._expressions = [] false_expression._expressions = [] self.convert_expressions(expression, true_expression, false_expression) @@ -137,8 +141,6 @@ def convert_expressions( # TODO: can we get rid of `NoneType` expressions in `TupleExpression`? # montyly: this might happen with unnamed tuple (ex: (,,,) = f()), but it needs to be checked if next_expr: - if isinstance(next_expr, IndexAccess): - self.convert_index_access(next_expr, true_expression, false_expression) if self.conditional_not_ahead( next_expr, true_expression, false_expression, f_expressions diff --git a/slither/utils/output.py b/slither/utils/output.py index 39218fbeb7..bbc2499fee 100644 --- a/slither/utils/output.py +++ b/slither/utils/output.py @@ -1,11 +1,12 @@ import hashlib -import os import json import logging +import os import zipfile from collections import OrderedDict -from typing import Optional, Dict, List, Union, Any, TYPE_CHECKING, Type +from typing import Tuple, Optional, Dict, List, Union, Any, TYPE_CHECKING, Type from zipfile import ZipFile + from pkg_resources import require from slither.core.cfg.node import Node @@ -220,7 +221,7 @@ def output_to_zip(filename: str, error: Optional[str], results: Dict, zip_type: ################################################################################### -def _convert_to_description(d): +def _convert_to_description(d: str) -> str: if isinstance(d, str): return d @@ -244,7 +245,7 @@ def _convert_to_description(d): raise SlitherError(f"{type(d)} cannot be converted (no name, or canonical_name") -def _convert_to_markdown(d, markdown_root): +def _convert_to_markdown(d: str, markdown_root: str) -> str: if isinstance(d, str): return d @@ -268,7 +269,7 @@ def _convert_to_markdown(d, markdown_root): raise SlitherError(f"{type(d)} cannot be converted (no name, or canonical_name") -def _convert_to_id(d): +def _convert_to_id(d: str) -> str: """ Id keeps the source mapping of the node, otherwise we risk to consider two different node as the same :param d: @@ -309,8 +310,35 @@ def _convert_to_id(d): def _create_base_element( - custom_type, name, source_mapping: Dict, type_specific_fields=None, additional_fields=None -): + custom_type: str, + name: str, + source_mapping: Dict, + type_specific_fields: Optional[ + Dict[ + str, + Union[ + Dict[ + str, + Union[ + str, + Dict[str, Union[int, str, bool, List[int]]], + Dict[ + str, + Union[ + Dict[str, Union[str, Dict[str, Union[int, str, bool, List[int]]]]], + str, + ], + ], + ], + ], + Dict[str, Union[str, Dict[str, Union[int, str, bool, List[int]]]]], + str, + List[str], + ], + ] + ] = None, + additional_fields: Optional[Dict[str, str]] = None, +) -> Dict[str, Any]: if additional_fields is None: additional_fields = {} if type_specific_fields is None: @@ -323,7 +351,16 @@ def _create_base_element( return element -def _create_parent_element(element): +def _create_parent_element( + element: SourceMapping, +) -> Dict[ + str, + Union[ + str, + Dict[str, Union[int, str, bool, List[int]]], + Dict[str, Union[Dict[str, Union[str, Dict[str, Union[int, str, bool, List[int]]]]], str]], + ], +]: # pylint: disable=import-outside-toplevel from slither.core.children.child_contract import ChildContract from slither.core.children.child_function import ChildFunction @@ -356,9 +393,9 @@ def __init__( self, info_: Union[str, List[Union[str, SupportedOutput]]], additional_fields: Optional[Dict] = None, - markdown_root="", - standard_format=True, - ): + markdown_root: str = "", + standard_format: bool = True, + ) -> None: if additional_fields is None: additional_fields = {} @@ -388,7 +425,7 @@ def __init__( if additional_fields: self._data["additional_fields"] = additional_fields - def add(self, add: SupportedOutput, additional_fields: Optional[Dict] = None): + def add(self, add: SupportedOutput, additional_fields: Optional[Dict] = None) -> None: if not self._data["first_markdown_element"]: self._data["first_markdown_element"] = add.source_mapping.to_markdown( self._markdown_root @@ -431,7 +468,7 @@ def elements(self) -> List[Dict]: ################################################################################### ################################################################################### - def add_variable(self, variable: Variable, additional_fields: Optional[Dict] = None): + def add_variable(self, variable: Variable, additional_fields: Optional[Dict] = None) -> None: if additional_fields is None: additional_fields = {} type_specific_fields = {"parent": _create_parent_element(variable)} @@ -457,7 +494,7 @@ def add_variables(self, variables: List[Variable]): ################################################################################### ################################################################################### - def add_contract(self, contract: Contract, additional_fields: Optional[Dict] = None): + def add_contract(self, contract: Contract, additional_fields: Optional[Dict] = None) -> None: if additional_fields is None: additional_fields = {} type_specific_fields = {"kind": "abstract" if contract.is_abstract else contract.contract_kind} @@ -473,7 +510,7 @@ def add_contract(self, contract: Contract, additional_fields: Optional[Dict] = N ################################################################################### ################################################################################### - def add_function(self, function: Function, additional_fields: Optional[Dict] = None): + def add_function(self, function: Function, additional_fields: Optional[Dict] = None) -> None: if additional_fields is None: additional_fields = {} type_specific_fields = { @@ -502,7 +539,7 @@ def add_functions(self, functions: List[Function], additional_fields: Optional[D ################################################################################### ################################################################################### - def add_enum(self, enum: Enum, additional_fields: Optional[Dict] = None): + def add_enum(self, enum: Enum, additional_fields: Optional[Dict] = None) -> None: if additional_fields is None: additional_fields = {} type_specific_fields = {"parent": _create_parent_element(enum)} @@ -522,7 +559,7 @@ def add_enum(self, enum: Enum, additional_fields: Optional[Dict] = None): ################################################################################### ################################################################################### - def add_struct(self, struct: Structure, additional_fields: Optional[Dict] = None): + def add_struct(self, struct: Structure, additional_fields: Optional[Dict] = None) -> None: if additional_fields is None: additional_fields = {} type_specific_fields = {"parent": _create_parent_element(struct)} @@ -542,7 +579,7 @@ def add_struct(self, struct: Structure, additional_fields: Optional[Dict] = None ################################################################################### ################################################################################### - def add_event(self, event: Event, additional_fields: Optional[Dict] = None): + def add_event(self, event: Event, additional_fields: Optional[Dict] = None) -> None: if additional_fields is None: additional_fields = {} type_specific_fields = { @@ -566,7 +603,7 @@ def add_event(self, event: Event, additional_fields: Optional[Dict] = None): ################################################################################### ################################################################################### - def add_node(self, node: Node, additional_fields: Optional[Dict] = None): + def add_node(self, node: Node, additional_fields: Optional[Dict] = None) -> None: if additional_fields is None: additional_fields = {} type_specific_fields = { @@ -593,7 +630,7 @@ def add_nodes(self, nodes: List[Node]): ################################################################################### ################################################################################### - def add_pragma(self, pragma: Pragma, additional_fields: Optional[Dict] = None): + def add_pragma(self, pragma: Pragma, additional_fields: Optional[Dict] = None) -> None: if additional_fields is None: additional_fields = {} type_specific_fields = {"directive": pragma.directive} @@ -666,10 +703,10 @@ def add_expression(self, expression: Expression, additional_fields: Optional[Dic def add_other( self, name: str, - source_mapping, + source_mapping: Tuple[str, int, int], compilation_unit: "SlitherCompilationUnit", additional_fields: Optional[Dict] = None, - ): + ) -> None: # If this a tuple with (filename, start, end), convert it to a source mapping. if additional_fields is None: additional_fields = {} diff --git a/slither/utils/source_mapping.py b/slither/utils/source_mapping.py index 26ad7c0d22..b117cd5f78 100644 --- a/slither/utils/source_mapping.py +++ b/slither/utils/source_mapping.py @@ -35,7 +35,7 @@ def get_definition(target: SourceMapping, crytic_compile: CryticCompile) -> Sour target.source_mapping.filename, target.source_mapping.start + start_offset + len(pattern) ) - s = Source() + s = Source(target.source_mapping.compilation_unit) s.start = target.source_mapping.start + start_offset s.length = len(pattern) s.filename = target.source_mapping.filename @@ -44,8 +44,7 @@ def get_definition(target: SourceMapping, crytic_compile: CryticCompile) -> Sour s.starting_column = starting_column s.ending_column = ending_column s.end = s.start + s.length - s.compilation_unit = target.compilation_unit - + s.txt = txt return s diff --git a/slither/utils/standard_libraries.py b/slither/utils/standard_libraries.py index 897954b958..5257711873 100644 --- a/slither/utils/standard_libraries.py +++ b/slither/utils/standard_libraries.py @@ -68,12 +68,7 @@ def is_openzeppelin(contract: "Contract") -> bool: def is_openzeppelin_strict(contract: "Contract") -> bool: - start = contract.source_mapping.start - end = start + contract.source_mapping.length - source_code = contract.compilation_unit.core.source_code[ - contract.source_mapping.filename.absolute - ][start:end] - source_hash = sha1(source_code.encode("utf-8")).hexdigest() + source_hash = sha1(contract.source_mapping.content.encode("utf-8")).hexdigest() return source_hash in oz_hashes diff --git a/slither/utils/type.py b/slither/utils/type.py index 5c7fcee344..1674999aa5 100644 --- a/slither/utils/type.py +++ b/slither/utils/type.py @@ -1,7 +1,13 @@ import math from typing import List, Union, Set -from slither.core.solidity_types import ArrayType, MappingType, ElementaryType, UserDefinedType +from slither.core.solidity_types import ( + ArrayType, + MappingType, + ElementaryType, + UserDefinedType, + TypeAlias, +) from slither.core.solidity_types.type import Type from slither.core.variables.variable import Variable @@ -89,6 +95,9 @@ def convert_type_for_solidity_signature(t: Type, seen: Set[Type]) -> Union[Type, ] return types + if isinstance(t, TypeAlias): + return t.type + return t diff --git a/slither/utils/type_helpers.py b/slither/utils/type_helpers.py index 522bade5d4..1f107e8c6e 100644 --- a/slither/utils/type_helpers.py +++ b/slither/utils/type_helpers.py @@ -11,7 +11,7 @@ ### core.declaration # pylint: disable=used-before-assignment -InternalCallType = Union[Function, SolidityFunction] -HighLevelCallType = Tuple[Contract, Union[Function, Variable]] -LibraryCallType = Tuple[Contract, Function] -LowLevelCallType = Tuple[Union[Variable, SolidityVariable], str] +InternalCallType = Union["Function", "SolidityFunction"] +HighLevelCallType = Tuple["Contract", Union["Function", "Variable"]] +LibraryCallType = Tuple["Contract", "Function"] +LowLevelCallType = Tuple[Union["Variable", "SolidityVariable"], str] diff --git a/slither/visitors/expression/constants_folding.py b/slither/visitors/expression/constants_folding.py index 797d1f46e4..5f419ef999 100644 --- a/slither/visitors/expression/constants_folding.py +++ b/slither/visitors/expression/constants_folding.py @@ -1,4 +1,6 @@ from fractions import Fraction +from typing import Union, TYPE_CHECKING + from slither.core.expressions import ( BinaryOperationType, Literal, @@ -6,10 +8,16 @@ Identifier, BinaryOperation, UnaryOperation, + TupleExpression, + TypeConversion, ) + from slither.utils.integer_conversion import convert_string_to_fraction, convert_string_to_int from slither.visitors.expression.expression import ExpressionVisitor +if TYPE_CHECKING: + from slither.core.solidity_types.elementary_type import ElementaryType + class NotConstant(Exception): pass @@ -17,24 +25,30 @@ class NotConstant(Exception): KEY = "ConstantFolding" +CONSTANT_TYPES_OPERATIONS = Union[ + Literal, BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion +] + -def get_val(expression): +def get_val(expression: CONSTANT_TYPES_OPERATIONS) -> Union[bool, int, Fraction, str]: val = expression.context[KEY] # we delete the item to reduce memory use del expression.context[KEY] return val -def set_val(expression, val): +def set_val(expression: CONSTANT_TYPES_OPERATIONS, val: Union[bool, int, Fraction, str]) -> None: expression.context[KEY] = val class ConstantFolding(ExpressionVisitor): - def __init__(self, expression, custom_type): + def __init__( + self, expression: CONSTANT_TYPES_OPERATIONS, custom_type: Union[str, "ElementaryType"] + ) -> None: self._type = custom_type super().__init__(expression) - def result(self): + def result(self) -> "Literal": value = get_val(self._expression) if isinstance(value, Fraction): value = int(value) @@ -43,7 +57,7 @@ def result(self): value = value & (2**256 - 1) return Literal(value, self._type) - def _post_identifier(self, expression: Identifier): + def _post_identifier(self, expression: Identifier) -> None: if not expression.value.is_constant: raise NotConstant expr = expression.value.expression @@ -54,7 +68,7 @@ def _post_identifier(self, expression: Identifier): set_val(expression, convert_string_to_int(expr.converted_value)) # pylint: disable=too-many-branches - def _post_binary_operation(self, expression: BinaryOperation): + def _post_binary_operation(self, expression: BinaryOperation) -> None: left = get_val(expression.expression_left) right = get_val(expression.expression_right) if expression.type == BinaryOperationType.POWER: @@ -100,7 +114,7 @@ def _post_binary_operation(self, expression: BinaryOperation): else: raise NotConstant - def _post_unary_operation(self, expression: UnaryOperation): + def _post_unary_operation(self, expression: UnaryOperation) -> None: # Case of uint a = -7; uint[-a] arr; if expression.type == UnaryOperationType.MINUS_PRE: expr = expression.expression @@ -112,7 +126,7 @@ def _post_unary_operation(self, expression: UnaryOperation): else: raise NotConstant - def _post_literal(self, expression: Literal): + def _post_literal(self, expression: Literal) -> None: if expression.converted_value in ["true", "false"]: set_val(expression, expression.converted_value) else: diff --git a/slither/visitors/expression/export_values.py b/slither/visitors/expression/export_values.py index 246cc8384e..f5ca39a969 100644 --- a/slither/visitors/expression/export_values.py +++ b/slither/visitors/expression/export_values.py @@ -1,21 +1,31 @@ +from typing import Any, List from slither.visitors.expression.expression import ExpressionVisitor +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.binary_operation import BinaryOperation +from slither.core.expressions.expression import Expression +from slither.core.expressions.member_access import MemberAccess +from slither.core.expressions.tuple_expression import TupleExpression +from slither.core.expressions.type_conversion import TypeConversion + key = "ExportValues" -def get(expression): +def get(expression: Expression) -> List[Any]: val = expression.context[key] # we delete the item to reduce memory use del expression.context[key] return val -def set_val(expression, val): +def set_val(expression: Expression, val: List[Any]) -> None: expression.context[key] = val class ExportValues(ExpressionVisitor): - def result(self): + def result(self) -> List[Any]: if self._result is None: self._result = list(set(get(self.expression))) return self._result @@ -26,13 +36,13 @@ def _post_assignement_operation(self, expression): val = left + right set_val(expression, val) - def _post_binary_operation(self, expression): + def _post_binary_operation(self, expression: BinaryOperation) -> None: left = get(expression.expression_left) right = get(expression.expression_right) val = left + right set_val(expression, val) - def _post_call_expression(self, expression): + def _post_call_expression(self, expression: CallExpression) -> None: called = get(expression.called) args = [get(a) for a in expression.arguments if a] args = [item for sublist in args for item in sublist] @@ -49,7 +59,7 @@ def _post_conditional_expression(self, expression): def _post_elementary_type_name_expression(self, expression): set_val(expression, []) - def _post_identifier(self, expression): + def _post_identifier(self, expression: Identifier) -> None: set_val(expression, [expression.value]) def _post_index_access(self, expression): @@ -58,10 +68,10 @@ def _post_index_access(self, expression): val = left + right set_val(expression, val) - def _post_literal(self, expression): + def _post_literal(self, expression: Literal) -> None: set_val(expression, []) - def _post_member_access(self, expression): + def _post_member_access(self, expression: MemberAccess) -> None: expr = get(expression.expression) val = expr set_val(expression, val) @@ -75,12 +85,12 @@ def _post_new_contract(self, expression): def _post_new_elementary_type(self, expression): set_val(expression, []) - def _post_tuple_expression(self, expression): + def _post_tuple_expression(self, expression: TupleExpression) -> None: expressions = [get(e) for e in expression.expressions if e] val = [item for sublist in expressions for item in sublist] set_val(expression, val) - def _post_type_conversion(self, expression): + def _post_type_conversion(self, expression: TypeConversion) -> None: expr = get(expression.expression) val = expr set_val(expression, val) diff --git a/slither/visitors/expression/expression.py b/slither/visitors/expression/expression.py index 17020aaba4..464ea12858 100644 --- a/slither/visitors/expression/expression.py +++ b/slither/visitors/expression/expression.py @@ -1,5 +1,5 @@ import logging -from typing import Any +from typing import Optional, Any from slither.core.expressions.assignment_operation import AssignmentOperation from slither.core.expressions.binary_operation import BinaryOperation @@ -23,13 +23,13 @@ class ExpressionVisitor: - def __init__(self, expression: Expression): + def __init__(self, expression: Expression) -> None: # Inherited class must declared their variables prior calling super().__init__ self._expression = expression self._result: Any = None self._visit_expression(self.expression) - def result(self): + def result(self) -> Optional[bool]: return self._result @property @@ -38,7 +38,8 @@ def expression(self) -> Expression: # visit an expression # call pre_visit, visit_expression_name, post_visit - def _visit_expression(self, expression: Expression): # pylint: disable=too-many-branches + # pylint: disable=too-many-branches + def _visit_expression(self, expression: Expression) -> None: self._pre_visit(expression) if isinstance(expression, AssignmentOperation): @@ -96,15 +97,15 @@ def _visit_expression(self, expression: Expression): # pylint: disable=too-many # visit_expression_name - def _visit_assignement_operation(self, expression): + def _visit_assignement_operation(self, expression: AssignmentOperation) -> None: self._visit_expression(expression.expression_left) self._visit_expression(expression.expression_right) - def _visit_binary_operation(self, expression): + def _visit_binary_operation(self, expression: BinaryOperation) -> None: self._visit_expression(expression.expression_left) self._visit_expression(expression.expression_right) - def _visit_call_expression(self, expression): + def _visit_call_expression(self, expression: CallExpression) -> None: self._visit_expression(expression.called) for arg in expression.arguments: if arg: @@ -116,50 +117,52 @@ def _visit_call_expression(self, expression): if expression.call_salt: self._visit_expression(expression.call_salt) - def _visit_conditional_expression(self, expression): + def _visit_conditional_expression(self, expression: ConditionalExpression) -> None: self._visit_expression(expression.if_expression) self._visit_expression(expression.else_expression) self._visit_expression(expression.then_expression) - def _visit_elementary_type_name_expression(self, expression): + def _visit_elementary_type_name_expression( + self, expression: ElementaryTypeNameExpression + ) -> None: pass - def _visit_identifier(self, expression): + def _visit_identifier(self, expression: Identifier) -> None: pass - def _visit_index_access(self, expression): + def _visit_index_access(self, expression: IndexAccess) -> None: self._visit_expression(expression.expression_left) self._visit_expression(expression.expression_right) - def _visit_literal(self, expression): + def _visit_literal(self, expression: Literal) -> None: pass - def _visit_member_access(self, expression): + def _visit_member_access(self, expression: MemberAccess) -> None: self._visit_expression(expression.expression) - def _visit_new_array(self, expression): + def _visit_new_array(self, expression: NewArray) -> None: pass - def _visit_new_contract(self, expression): + def _visit_new_contract(self, expression: NewContract) -> None: pass def _visit_new_elementary_type(self, expression): pass - def _visit_tuple_expression(self, expression): + def _visit_tuple_expression(self, expression: TupleExpression) -> None: for e in expression.expressions: if e: self._visit_expression(e) - def _visit_type_conversion(self, expression): + def _visit_type_conversion(self, expression: TypeConversion) -> None: self._visit_expression(expression.expression) - def _visit_unary_operation(self, expression): + def _visit_unary_operation(self, expression: UnaryOperation) -> None: self._visit_expression(expression.expression) # pre visit - def _pre_visit(self, expression): # pylint: disable=too-many-branches + def _pre_visit(self, expression) -> None: # pylint: disable=too-many-branches if isinstance(expression, AssignmentOperation): self._pre_assignement_operation(expression) @@ -213,54 +216,56 @@ def _pre_visit(self, expression): # pylint: disable=too-many-branches # pre_expression_name - def _pre_assignement_operation(self, expression): + def _pre_assignement_operation(self, expression: AssignmentOperation) -> None: pass - def _pre_binary_operation(self, expression): + def _pre_binary_operation(self, expression: BinaryOperation) -> None: pass - def _pre_call_expression(self, expression): + def _pre_call_expression(self, expression: CallExpression) -> None: pass - def _pre_conditional_expression(self, expression): + def _pre_conditional_expression(self, expression: ConditionalExpression) -> None: pass - def _pre_elementary_type_name_expression(self, expression): + def _pre_elementary_type_name_expression( + self, expression: ElementaryTypeNameExpression + ) -> None: pass - def _pre_identifier(self, expression): + def _pre_identifier(self, expression: Identifier) -> None: pass - def _pre_index_access(self, expression): + def _pre_index_access(self, expression: IndexAccess) -> None: pass - def _pre_literal(self, expression): + def _pre_literal(self, expression: Literal) -> None: pass - def _pre_member_access(self, expression): + def _pre_member_access(self, expression: MemberAccess) -> None: pass - def _pre_new_array(self, expression): + def _pre_new_array(self, expression: NewArray) -> None: pass - def _pre_new_contract(self, expression): + def _pre_new_contract(self, expression: NewContract) -> None: pass def _pre_new_elementary_type(self, expression): pass - def _pre_tuple_expression(self, expression): + def _pre_tuple_expression(self, expression: TupleExpression) -> None: pass - def _pre_type_conversion(self, expression): + def _pre_type_conversion(self, expression: TypeConversion) -> None: pass - def _pre_unary_operation(self, expression): + def _pre_unary_operation(self, expression: UnaryOperation) -> None: pass # post visit - def _post_visit(self, expression): # pylint: disable=too-many-branches + def _post_visit(self, expression) -> None: # pylint: disable=too-many-branches if isinstance(expression, AssignmentOperation): self._post_assignement_operation(expression) @@ -314,47 +319,49 @@ def _post_visit(self, expression): # pylint: disable=too-many-branches # post_expression_name - def _post_assignement_operation(self, expression): + def _post_assignement_operation(self, expression: AssignmentOperation) -> None: pass - def _post_binary_operation(self, expression): + def _post_binary_operation(self, expression: BinaryOperation) -> None: pass - def _post_call_expression(self, expression): + def _post_call_expression(self, expression: CallExpression) -> None: pass def _post_conditional_expression(self, expression): pass - def _post_elementary_type_name_expression(self, expression): + def _post_elementary_type_name_expression( + self, expression: ElementaryTypeNameExpression + ) -> None: pass - def _post_identifier(self, expression): + def _post_identifier(self, expression: Identifier) -> None: pass - def _post_index_access(self, expression): + def _post_index_access(self, expression: IndexAccess) -> None: pass - def _post_literal(self, expression): + def _post_literal(self, expression: Literal) -> None: pass - def _post_member_access(self, expression): + def _post_member_access(self, expression: MemberAccess) -> None: pass - def _post_new_array(self, expression): + def _post_new_array(self, expression: NewArray) -> None: pass - def _post_new_contract(self, expression): + def _post_new_contract(self, expression: NewContract) -> None: pass def _post_new_elementary_type(self, expression): pass - def _post_tuple_expression(self, expression): + def _post_tuple_expression(self, expression: TupleExpression) -> None: pass - def _post_type_conversion(self, expression): + def _post_type_conversion(self, expression: TypeConversion) -> None: pass - def _post_unary_operation(self, expression): + def _post_unary_operation(self, expression: UnaryOperation) -> None: pass diff --git a/slither/visitors/expression/find_calls.py b/slither/visitors/expression/find_calls.py index 9b9141c76c..6653a97592 100644 --- a/slither/visitors/expression/find_calls.py +++ b/slither/visitors/expression/find_calls.py @@ -1,19 +1,33 @@ -from typing import List +from typing import Any, Union, List from slither.core.expressions.expression import Expression from slither.visitors.expression.expression import ExpressionVisitor +from slither.core.expressions.assignment_operation import AssignmentOperation +from slither.core.expressions.binary_operation import BinaryOperation +from slither.core.expressions.call_expression import CallExpression +from slither.core.expressions.conditional_expression import ConditionalExpression +from slither.core.expressions.elementary_type_name_expression import ElementaryTypeNameExpression +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.member_access import MemberAccess +from slither.core.expressions.new_array import NewArray +from slither.core.expressions.new_contract import NewContract +from slither.core.expressions.tuple_expression import TupleExpression +from slither.core.expressions.type_conversion import TypeConversion +from slither.core.expressions.unary_operation import UnaryOperation key = "FindCall" -def get(expression): +def get(expression: Expression) -> List[Union[Any, CallExpression]]: val = expression.context[key] # we delete the item to reduce memory use del expression.context[key] return val -def set_val(expression, val): +def set_val(expression: Expression, val: List[Union[Any, CallExpression]]) -> None: expression.context[key] = val @@ -23,19 +37,19 @@ def result(self) -> List[Expression]: self._result = list(set(get(self.expression))) return self._result - def _post_assignement_operation(self, expression): + def _post_assignement_operation(self, expression: AssignmentOperation) -> None: left = get(expression.expression_left) right = get(expression.expression_right) val = left + right set_val(expression, val) - def _post_binary_operation(self, expression): + def _post_binary_operation(self, expression: BinaryOperation) -> None: left = get(expression.expression_left) right = get(expression.expression_right) val = left + right set_val(expression, val) - def _post_call_expression(self, expression): + def _post_call_expression(self, expression: CallExpression) -> None: called = get(expression.called) args = [get(a) for a in expression.arguments if a] args = [item for sublist in args for item in sublist] @@ -43,54 +57,56 @@ def _post_call_expression(self, expression): val += [expression] set_val(expression, val) - def _post_conditional_expression(self, expression): + def _post_conditional_expression(self, expression: ConditionalExpression) -> None: if_expr = get(expression.if_expression) else_expr = get(expression.else_expression) then_expr = get(expression.then_expression) val = if_expr + else_expr + then_expr set_val(expression, val) - def _post_elementary_type_name_expression(self, expression): + def _post_elementary_type_name_expression( + self, expression: ElementaryTypeNameExpression + ) -> None: set_val(expression, []) # save only identifier expression - def _post_identifier(self, expression): + def _post_identifier(self, expression: Identifier) -> None: set_val(expression, []) - def _post_index_access(self, expression): + def _post_index_access(self, expression: IndexAccess) -> None: left = get(expression.expression_left) right = get(expression.expression_right) val = left + right set_val(expression, val) - def _post_literal(self, expression): + def _post_literal(self, expression: Literal) -> None: set_val(expression, []) - def _post_member_access(self, expression): + def _post_member_access(self, expression: MemberAccess) -> None: expr = get(expression.expression) val = expr set_val(expression, val) - def _post_new_array(self, expression): + def _post_new_array(self, expression: NewArray) -> None: set_val(expression, []) - def _post_new_contract(self, expression): + def _post_new_contract(self, expression: NewContract) -> None: set_val(expression, []) def _post_new_elementary_type(self, expression): set_val(expression, []) - def _post_tuple_expression(self, expression): + def _post_tuple_expression(self, expression: TupleExpression) -> None: expressions = [get(e) for e in expression.expressions if e] val = [item for sublist in expressions for item in sublist] set_val(expression, val) - def _post_type_conversion(self, expression): + def _post_type_conversion(self, expression: TypeConversion) -> None: expr = get(expression.expression) val = expr set_val(expression, val) - def _post_unary_operation(self, expression): + def _post_unary_operation(self, expression: UnaryOperation) -> None: expr = get(expression.expression) val = expr set_val(expression, val) diff --git a/slither/visitors/expression/has_conditional.py b/slither/visitors/expression/has_conditional.py index 906f522ff3..b866a696b5 100644 --- a/slither/visitors/expression/has_conditional.py +++ b/slither/visitors/expression/has_conditional.py @@ -1,12 +1,13 @@ from slither.visitors.expression.expression import ExpressionVisitor +from slither.core.expressions.conditional_expression import ConditionalExpression class HasConditional(ExpressionVisitor): - def result(self): + def result(self) -> bool: # == True, to convert None to false return self._result is True - def _post_conditional_expression(self, expression): + def _post_conditional_expression(self, expression: ConditionalExpression) -> None: # if self._result is True: # raise('Slither does not support nested ternary operator') self._result = True diff --git a/slither/visitors/expression/read_var.py b/slither/visitors/expression/read_var.py index 8fe063a2f3..e8f5aae67e 100644 --- a/slither/visitors/expression/read_var.py +++ b/slither/visitors/expression/read_var.py @@ -1,38 +1,58 @@ +from typing import Any, List, Union + from slither.visitors.expression.expression import ExpressionVisitor -from slither.core.expressions.assignment_operation import AssignmentOperationType +from slither.core.expressions.assignment_operation import ( + AssignmentOperation, + AssignmentOperationType, +) from slither.core.variables.variable import Variable from slither.core.declarations.solidity_variables import SolidityVariable +from slither.core.expressions.binary_operation import BinaryOperation +from slither.core.expressions.call_expression import CallExpression +from slither.core.expressions.conditional_expression import ConditionalExpression +from slither.core.expressions.elementary_type_name_expression import ElementaryTypeNameExpression +from slither.core.expressions.expression import Expression +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.member_access import MemberAccess +from slither.core.expressions.new_array import NewArray +from slither.core.expressions.new_contract import NewContract +from slither.core.expressions.tuple_expression import TupleExpression +from slither.core.expressions.type_conversion import TypeConversion +from slither.core.expressions.unary_operation import UnaryOperation + key = "ReadVar" -def get(expression): +def get(expression: Expression) -> List[Union[Identifier, IndexAccess, Any]]: val = expression.context[key] # we delete the item to reduce memory use del expression.context[key] return val -def set_val(expression, val): +def set_val(expression: Expression, val: List[Union[Identifier, IndexAccess, Any]]) -> None: expression.context[key] = val class ReadVar(ExpressionVisitor): - def result(self): + def result(self) -> List[Union[Identifier, IndexAccess, Any]]: if self._result is None: self._result = list(set(get(self.expression))) return self._result # overide assignement # dont explore if its direct assignement (we explore if its +=, -=, ...) - def _visit_assignement_operation(self, expression): + def _visit_assignement_operation(self, expression: AssignmentOperation) -> None: if expression.type != AssignmentOperationType.ASSIGN: self._visit_expression(expression.expression_left) self._visit_expression(expression.expression_right) - def _post_assignement_operation(self, expression): + def _post_assignement_operation(self, expression: AssignmentOperation) -> None: if expression.type != AssignmentOperationType.ASSIGN: left = get(expression.expression_left) else: @@ -41,31 +61,33 @@ def _post_assignement_operation(self, expression): val = left + right set_val(expression, val) - def _post_binary_operation(self, expression): + def _post_binary_operation(self, expression: BinaryOperation) -> None: left = get(expression.expression_left) right = get(expression.expression_right) val = left + right set_val(expression, val) - def _post_call_expression(self, expression): + def _post_call_expression(self, expression: CallExpression) -> None: called = get(expression.called) args = [get(a) for a in expression.arguments if a] args = [item for sublist in args for item in sublist] val = called + args set_val(expression, val) - def _post_conditional_expression(self, expression): + def _post_conditional_expression(self, expression: ConditionalExpression) -> None: if_expr = get(expression.if_expression) else_expr = get(expression.else_expression) then_expr = get(expression.then_expression) val = if_expr + else_expr + then_expr set_val(expression, val) - def _post_elementary_type_name_expression(self, expression): + def _post_elementary_type_name_expression( + self, expression: ElementaryTypeNameExpression + ) -> None: set_val(expression, []) # save only identifier expression - def _post_identifier(self, expression): + def _post_identifier(self, expression: Identifier) -> None: if isinstance(expression.value, Variable): set_val(expression, [expression]) elif isinstance(expression.value, SolidityVariable): @@ -73,40 +95,40 @@ def _post_identifier(self, expression): else: set_val(expression, []) - def _post_index_access(self, expression): + def _post_index_access(self, expression: IndexAccess) -> None: left = get(expression.expression_left) right = get(expression.expression_right) val = left + right + [expression] set_val(expression, val) - def _post_literal(self, expression): + def _post_literal(self, expression: Literal) -> None: set_val(expression, []) - def _post_member_access(self, expression): + def _post_member_access(self, expression: MemberAccess) -> None: expr = get(expression.expression) val = expr set_val(expression, val) - def _post_new_array(self, expression): + def _post_new_array(self, expression: NewArray) -> None: set_val(expression, []) - def _post_new_contract(self, expression): + def _post_new_contract(self, expression: NewContract) -> None: set_val(expression, []) def _post_new_elementary_type(self, expression): set_val(expression, []) - def _post_tuple_expression(self, expression): + def _post_tuple_expression(self, expression: TupleExpression) -> None: expressions = [get(e) for e in expression.expressions if e] val = [item for sublist in expressions for item in sublist] set_val(expression, val) - def _post_type_conversion(self, expression): + def _post_type_conversion(self, expression: TypeConversion) -> None: expr = get(expression.expression) val = expr set_val(expression, val) - def _post_unary_operation(self, expression): + def _post_unary_operation(self, expression: UnaryOperation) -> None: expr = get(expression.expression) val = expr set_val(expression, val) diff --git a/slither/visitors/expression/write_var.py b/slither/visitors/expression/write_var.py index 0509a2eb71..97d3858e7e 100644 --- a/slither/visitors/expression/write_var.py +++ b/slither/visitors/expression/write_var.py @@ -1,26 +1,43 @@ +from typing import Any, List from slither.visitors.expression.expression import ExpressionVisitor +from slither.core.expressions.assignment_operation import AssignmentOperation +from slither.core.expressions.binary_operation import BinaryOperation +from slither.core.expressions.call_expression import CallExpression +from slither.core.expressions.conditional_expression import ConditionalExpression +from slither.core.expressions.elementary_type_name_expression import ElementaryTypeNameExpression +from slither.core.expressions.expression import Expression +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.member_access import MemberAccess +from slither.core.expressions.new_array import NewArray +from slither.core.expressions.new_contract import NewContract +from slither.core.expressions.tuple_expression import TupleExpression +from slither.core.expressions.type_conversion import TypeConversion +from slither.core.expressions.unary_operation import UnaryOperation + key = "WriteVar" -def get(expression): +def get(expression: Expression) -> List[Any]: val = expression.context[key] # we delete the item to reduce memory use del expression.context[key] return val -def set_val(expression, val): +def set_val(expression: Expression, val: List[Any]) -> None: expression.context[key] = val class WriteVar(ExpressionVisitor): - def result(self): + def result(self) -> List[Any]: if self._result is None: self._result = list(set(get(self.expression))) return self._result - def _post_binary_operation(self, expression): + def _post_binary_operation(self, expression: BinaryOperation) -> None: left = get(expression.expression_left) right = get(expression.expression_right) val = left + right @@ -28,7 +45,7 @@ def _post_binary_operation(self, expression): val += [expression] set_val(expression, val) - def _post_call_expression(self, expression): + def _post_call_expression(self, expression: CallExpression) -> None: called = get(expression.called) args = [get(a) for a in expression.arguments if a] args = [item for sublist in args for item in sublist] @@ -37,7 +54,7 @@ def _post_call_expression(self, expression): val += [expression] set_val(expression, val) - def _post_conditional_expression(self, expression): + def _post_conditional_expression(self, expression: ConditionalExpression) -> None: if_expr = get(expression.if_expression) else_expr = get(expression.else_expression) then_expr = get(expression.then_expression) @@ -46,7 +63,7 @@ def _post_conditional_expression(self, expression): val += [expression] set_val(expression, val) - def _post_assignement_operation(self, expression): + def _post_assignement_operation(self, expression: AssignmentOperation) -> None: left = get(expression.expression_left) right = get(expression.expression_right) val = left + right @@ -54,11 +71,13 @@ def _post_assignement_operation(self, expression): val += [expression] set_val(expression, val) - def _post_elementary_type_name_expression(self, expression): + def _post_elementary_type_name_expression( + self, expression: ElementaryTypeNameExpression + ) -> None: set_val(expression, []) # save only identifier expression - def _post_identifier(self, expression): + def _post_identifier(self, expression: Identifier) -> None: if expression.is_lvalue: set_val(expression, [expression]) else: @@ -69,7 +88,7 @@ def _post_identifier(self, expression): # else: # set_val(expression, []) - def _post_index_access(self, expression): + def _post_index_access(self, expression: IndexAccess) -> None: left = get(expression.expression_left) right = get(expression.expression_right) val = left + right @@ -87,10 +106,10 @@ def _post_index_access(self, expression): # n = n.expression set_val(expression, val) - def _post_literal(self, expression): + def _post_literal(self, expression: Literal) -> None: set_val(expression, []) - def _post_member_access(self, expression): + def _post_member_access(self, expression: MemberAccess) -> None: expr = get(expression.expression) val = expr if expression.is_lvalue: @@ -98,30 +117,30 @@ def _post_member_access(self, expression): val += [expression.expression] set_val(expression, val) - def _post_new_array(self, expression): + def _post_new_array(self, expression: NewArray) -> None: set_val(expression, []) - def _post_new_contract(self, expression): + def _post_new_contract(self, expression: NewContract) -> None: set_val(expression, []) def _post_new_elementary_type(self, expression): set_val(expression, []) - def _post_tuple_expression(self, expression): + def _post_tuple_expression(self, expression: TupleExpression) -> None: expressions = [get(e) for e in expression.expressions if e] val = [item for sublist in expressions for item in sublist] if expression.is_lvalue: val += [expression] set_val(expression, val) - def _post_type_conversion(self, expression): + def _post_type_conversion(self, expression: TypeConversion) -> None: expr = get(expression.expression) val = expr if expression.is_lvalue: val += [expression] set_val(expression, val) - def _post_unary_operation(self, expression): + def _post_unary_operation(self, expression: UnaryOperation) -> None: expr = get(expression.expression) val = expr if expression.is_lvalue: diff --git a/slither/visitors/slithir/expression_to_slithir.py b/slither/visitors/slithir/expression_to_slithir.py index 7db581135b..c39a0a5539 100644 --- a/slither/visitors/slithir/expression_to_slithir.py +++ b/slither/visitors/slithir/expression_to_slithir.py @@ -1,6 +1,5 @@ import logging - -from typing import List +from typing import Union, List, TYPE_CHECKING from slither.core.declarations import ( Function, @@ -11,6 +10,7 @@ ) from slither.core.declarations.enum import Enum from slither.core.expressions import ( + AssignmentOperation, AssignmentOperationType, UnaryOperationType, BinaryOperationType, @@ -19,9 +19,19 @@ Identifier, MemberAccess, ) +from slither.core.expressions.binary_operation import BinaryOperation +from slither.core.expressions.expression import Expression +from slither.core.expressions.index_access import IndexAccess +from slither.core.expressions.literal import Literal +from slither.core.expressions.new_array import NewArray +from slither.core.expressions.new_contract import NewContract +from slither.core.expressions.tuple_expression import TupleExpression +from slither.core.expressions.unary_operation import UnaryOperation from slither.core.solidity_types import ArrayType, ElementaryType, TypeAlias from slither.core.solidity_types.type import Type +from slither.core.variables.local_variable import LocalVariable from slither.core.variables.local_variable_init_from_tuple import LocalVariableInitFromTuple +from slither.core.variables.state_variable import StateVariable from slither.core.variables.variable import Variable from slither.slithir.exceptions import SlithIRError from slither.slithir.operations import ( @@ -54,12 +64,15 @@ from slither.visitors.expression.expression import ExpressionVisitor from slither.visitors.expression.constants_folding import ConstantFolding, NotConstant +if TYPE_CHECKING: + from slither.core.cfg.node import Node + logger = logging.getLogger("VISTIOR:ExpressionToSlithIR") key = "expressionToSlithIR" -def get(expression): +def get(expression: Union[Expression, Operation]): val = expression.context[key] # we delete the item to reduce memory use del expression.context[key] @@ -70,7 +83,7 @@ def get_without_removing(expression): return expression.context[key] -def set_val(expression, val): +def set_val(expression: Union[Expression, Operation], val) -> None: expression.context[key] = val @@ -105,7 +118,12 @@ def set_val(expression, val): } -def convert_assignment(left, right, t, return_type): +def convert_assignment( + left: Union[LocalVariable, StateVariable, ReferenceVariable], + right: Union[LocalVariable, StateVariable, ReferenceVariable], + t: AssignmentOperationType, + return_type, +) -> Union[Binary, Assignment]: if t == AssignmentOperationType.ASSIGN: return Assignment(left, right, return_type) if t == AssignmentOperationType.ASSIGN_OR: @@ -133,7 +151,8 @@ def convert_assignment(left, right, t, return_type): class ExpressionToSlithIR(ExpressionVisitor): - def __init__(self, expression, node): # pylint: disable=super-init-not-called + # pylint: disable=super-init-not-called + def __init__(self, expression: Expression, node: "Node") -> None: from slither.core.cfg.node import NodeType # pylint: disable=import-outside-toplevel self._expression = expression @@ -147,10 +166,10 @@ def __init__(self, expression, node): # pylint: disable=super-init-not-called for ir in self._result: ir.set_node(node) - def result(self): + def result(self) -> List[Operation]: return self._result - def _post_assignement_operation(self, expression): + def _post_assignement_operation(self, expression: AssignmentOperation) -> None: left = get(expression.expression_left) right = get(expression.expression_right) if isinstance(left, list): # tuple expression: @@ -202,6 +221,14 @@ def _post_assignement_operation(self, expression): operation.set_expression(expression) self._result.append(operation) set_val(expression, left) + # Disabled https://github.com/crytic/slither/pull/1761 here. + # We prefer https://github.com/CertiKProject/slither-certik/pull/26. + # elif isinstance(left.type, ArrayType): + # # Special case for init of array, when the right has only one element + # operation = InitArray([right], left) + # operation.set_expression(expression) + # self._result.append(operation) + # set_val(expression, left) else: operation = convert_assignment( left, right, expression.type, expression.expression_return_type @@ -235,7 +262,7 @@ def _attempt_constant_folding(self, expression): set_val(expression, cst) return True - def _post_binary_operation(self, expression): + def _post_binary_operation(self, expression: BinaryOperation) -> None: if self._node.compilation_unit.generates_certik_ir and self._attempt_constant_folding(expression): return @@ -275,9 +302,8 @@ def _post_binary_operation(self, expression): set_val(expression, val) - def _post_call_expression( - self, expression - ): # pylint: disable=too-many-branches,too-many-statements,too-many-locals + # pylint: disable=too-many-branches,too-many-statements,too-many-locals + def _post_call_expression(self, expression: CallExpression) -> None: assert isinstance(expression, CallExpression) @@ -389,13 +415,16 @@ def _post_call_expression( def _post_conditional_expression(self, expression): raise Exception(f"Ternary operator are not convertible to SlithIR {expression}") - def _post_elementary_type_name_expression(self, expression): + def _post_elementary_type_name_expression( + self, + expression: ElementaryTypeNameExpression, + ) -> None: set_val(expression, expression.type) - def _post_identifier(self, expression): + def _post_identifier(self, expression: Identifier) -> None: set_val(expression, expression.value) - def _post_index_access(self, expression): + def _post_index_access(self, expression: IndexAccess) -> None: left = get(expression.expression_left) right = get(expression.expression_right) # Left can be a type for abi.decode(var, uint[2]) @@ -421,11 +450,11 @@ def _post_index_access(self, expression): self._result.append(operation) set_val(expression, val) - def _post_literal(self, expression): + def _post_literal(self, expression: Literal) -> None: cst = Constant(expression.value, expression.type, expression.subdenomination) set_val(expression, cst) - def _post_member_access(self, expression): + def _post_member_access(self, expression: MemberAccess) -> None: expr = get(expression.expression) # Look for type(X).max / min @@ -510,14 +539,14 @@ def _post_member_access(self, expression): self._result.append(member) set_val(expression, val) - def _post_new_array(self, expression): + def _post_new_array(self, expression: NewArray) -> None: val = TemporaryVariable(self._node) operation = TmpNewArray(expression.depth, expression.array_type, val) operation.set_expression(expression) self._result.append(operation) set_val(expression, val) - def _post_new_contract(self, expression): + def _post_new_contract(self, expression: NewContract) -> None: val = TemporaryVariable(self._node) operation = TmpNewContract(expression.contract_name, val) operation.set_expression(expression) @@ -539,7 +568,7 @@ def _post_new_elementary_type(self, expression): self._result.append(operation) set_val(expression, val) - def _post_tuple_expression(self, expression): + def _post_tuple_expression(self, expression: TupleExpression) -> None: expressions = [get(e) if e else None for e in expression.expressions] if expression.is_inline_array: temp_var = TemporaryVariable(self._node) @@ -553,7 +582,7 @@ def _post_tuple_expression(self, expression): val = expressions set_val(expression, val) - def _post_type_conversion(self, expression): + def _post_type_conversion(self, expression: TypeConversion) -> None: expr = get(expression.expression) val = TemporaryVariable(self._node) operation = TypeConversion(val, expr, expression.type) @@ -562,9 +591,8 @@ def _post_type_conversion(self, expression): self._result.append(operation) set_val(expression, val) - def _post_unary_operation( - self, expression - ): # pylint: disable=too-many-branches,too-many-statements + # pylint: disable=too-many-statements + def _post_unary_operation(self, expression: UnaryOperation) -> None: if self._node.compilation_unit.generates_certik_ir and self._attempt_constant_folding(expression): return diff --git a/tests/arithmetic_usage/test.sol b/tests/arithmetic_usage/test.sol new file mode 100644 index 0000000000..7bda7e5042 --- /dev/null +++ b/tests/arithmetic_usage/test.sol @@ -0,0 +1,29 @@ +function protected(uint a, uint b) returns(uint){ + return (a + b) * (a + b); +} + +function not_protected_asm(uint a, uint b) returns(uint){ + uint c; + assembly{ + c := mul(add(a,b), add(a,b)) + } + return c; +} + +function not_protected_unchecked(uint a, uint b) returns(uint){ + uint c; + unchecked{ + return (a + b) * (a + b); + } + +} + +contract A{ + + function f(uint a, uint b) public{ + protected(a,b); + not_protected_asm(a, b); + not_protected_unchecked(a, b); + } + +} \ No newline at end of file diff --git a/tests/ast-parsing/compile/yul-state-constant-access.sol-0.8.16-compact.zip b/tests/ast-parsing/compile/yul-state-constant-access.sol-0.8.16-compact.zip new file mode 100644 index 0000000000..121d6c5ec6 Binary files /dev/null and b/tests/ast-parsing/compile/yul-state-constant-access.sol-0.8.16-compact.zip differ diff --git a/tests/ast-parsing/expected/yul-state-constant-access.sol-0.8.16-compact.json b/tests/ast-parsing/expected/yul-state-constant-access.sol-0.8.16-compact.json new file mode 100644 index 0000000000..6c6bc93779 --- /dev/null +++ b/tests/ast-parsing/expected/yul-state-constant-access.sol-0.8.16-compact.json @@ -0,0 +1,12 @@ +{ + "A": { + "f()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "f2()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: NEW VARIABLE 1\n\"];\n1->2;\n2[label=\"Node Type: INLINE ASM 2\n\"];\n2->3;\n3[label=\"Node Type: EXPRESSION 3\n\"];\n}\n", + "f3()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: NEW VARIABLE 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n" + }, + "B": { + "f()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n", + "f2()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: NEW VARIABLE 1\n\"];\n1->2;\n2[label=\"Node Type: INLINE ASM 2\n\"];\n2->3;\n3[label=\"Node Type: EXPRESSION 3\n\"];\n}\n", + "f3()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: NEW VARIABLE 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n" + } +} \ No newline at end of file diff --git a/tests/ast-parsing/yul-state-constant-access.sol b/tests/ast-parsing/yul-state-constant-access.sol new file mode 100644 index 0000000000..543d797446 --- /dev/null +++ b/tests/ast-parsing/yul-state-constant-access.sol @@ -0,0 +1,25 @@ +contract A{ + uint private constant a = 10; + + function f() public returns(uint){ + return a; + } + + function f2() public returns(uint){ + uint ret; + assembly{ + ret := a + } + } + + function f3() public returns(uint){ + uint ret; + unchecked{ + ret = a; + } + } +} + +contract B is A{ + +} \ No newline at end of file diff --git a/tests/check-upgradeability/contractV1_struct.sol b/tests/check-upgradeability/contractV1_struct.sol new file mode 100644 index 0000000000..cd0e90bd48 --- /dev/null +++ b/tests/check-upgradeability/contractV1_struct.sol @@ -0,0 +1,8 @@ +contract ContractV1{ + struct Foo { + uint256 bar; + address baz; + } + address destination; + Foo foo; +} diff --git a/tests/check-upgradeability/contractV2_struct.sol b/tests/check-upgradeability/contractV2_struct.sol new file mode 100644 index 0000000000..76175d0044 --- /dev/null +++ b/tests/check-upgradeability/contractV2_struct.sol @@ -0,0 +1,8 @@ +contract ContractV2{ + struct Foo { + uint256 bar; + address baz; + } + address destination; + Foo foo; +} diff --git a/tests/check-upgradeability/contractV2_struct_bug.sol b/tests/check-upgradeability/contractV2_struct_bug.sol new file mode 100644 index 0000000000..2e3b0da343 --- /dev/null +++ b/tests/check-upgradeability/contractV2_struct_bug.sol @@ -0,0 +1,8 @@ +contract ContractV2{ + struct Foo { + uint8 bar; + address baz; + } + address destination; + Foo foo; +} diff --git a/tests/check-upgradeability/test_12.txt b/tests/check-upgradeability/test_12.txt new file mode 100644 index 0000000000..353d8ebdb7 --- /dev/null +++ b/tests/check-upgradeability/test_12.txt @@ -0,0 +1,7 @@ +INFO:Slither: +Initializable contract not found, the contract does not follow a standard initalization schema. +Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#initializable-is-missing +INFO:Slither: +Initializable contract not found, the contract does not follow a standard initalization schema. +Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#initializable-is-missing +INFO:Slither:2 findings, 21 detectors run diff --git a/tests/check-upgradeability/test_13.txt b/tests/check-upgradeability/test_13.txt new file mode 100644 index 0000000000..9635f9a43b --- /dev/null +++ b/tests/check-upgradeability/test_13.txt @@ -0,0 +1,12 @@ +INFO:Slither: +Initializable contract not found, the contract does not follow a standard initalization schema. +Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#initializable-is-missing +INFO:Slither: +Different variables between ContractV1 (tests/check-upgradeability/contractV1_struct.sol#1-8) and ContractV2 (tests/check-upgradeability/contractV2_struct_bug.sol#1-8) + ContractV1.foo (tests/check-upgradeability/contractV1_struct.sol#7) + ContractV2.foo (tests/check-upgradeability/contractV2_struct_bug.sol#7) +Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#incorrect-variables-with-the-v2 +INFO:Slither: +Initializable contract not found, the contract does not follow a standard initalization schema. +Reference: https://github.com/crytic/slither/wiki/Upgradeability-Checks#initializable-is-missing +INFO:Slither:3 findings, 21 detectors run diff --git a/tests/custom_comments/contract_comment.sol b/tests/custom_comments/contract_comment.sol new file mode 100644 index 0000000000..8f0fb5233b --- /dev/null +++ b/tests/custom_comments/contract_comment.sol @@ -0,0 +1,7 @@ +/** + * @title Test Contract + * @dev Test comment + */ +contract A{ + +} \ No newline at end of file diff --git a/tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol b/tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol new file mode 100644 index 0000000000..cd62827dc9 --- /dev/null +++ b/tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol @@ -0,0 +1,32 @@ +pragma solidity 0.8.16; + +contract HighCyclomaticComplexity +{ + bool bool1; + bool bool2; + bool bool3; + bool bool4; + bool bool5; + bool bool6; + bool bool7; + bool bool8; + bool bool9; + bool bool10; + bool bool11; + + function highCC() internal view + { + if (bool1) + if (bool2) + if (bool3) + if (bool4) + if (bool5) + if (bool6) + if (bool7) + if (bool8) + if (bool9) + if (bool10) + if (bool11) + revert(); + } +} \ No newline at end of file diff --git a/tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol.0.8.16.CyclomaticComplexity.json b/tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol.0.8.16.CyclomaticComplexity.json new file mode 100644 index 0000000000..e6e370ac36 --- /dev/null +++ b/tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol.0.8.16.CyclomaticComplexity.json @@ -0,0 +1,96 @@ +[ + [ + { + "elements": [ + { + "type": "function", + "name": "highCC", + "source_mapping": { + "start": 244, + "length": 536, + "filename_relative": "tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol", + "is_dependency": false, + "lines": [ + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "HighCyclomaticComplexity", + "source_mapping": { + "start": 25, + "length": 757, + "filename_relative": "tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol", + "is_dependency": false, + "lines": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33 + ], + "starting_column": 1, + "ending_column": 0 + } + }, + "signature": "highCC()" + } + } + ], + "description": "HighCyclomaticComplexity.highCC() (tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol#17-31) has a high cyclomatic complexity (12).\n", + "markdown": "[HighCyclomaticComplexity.highCC()](tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol#L17-L31) has a high cyclomatic complexity (12).\n", + "first_markdown_element": "tests/detectors/cyclomatic-complexity/0.8.16/HighCyclomaticComplexity.sol#L17-L31", + "id": "405b9e7f5697539c75171d728f0a10b6ebb7fe08441c445b0e63c33982c98e2d", + "check": "cyclomatic-complexity", + "impact": "Informational", + "confidence": "High" + } + ] +] \ No newline at end of file diff --git a/tests/detectors/cyclomatic-complexity/0.8.16/LowCyclomaticComplexity.sol b/tests/detectors/cyclomatic-complexity/0.8.16/LowCyclomaticComplexity.sol new file mode 100644 index 0000000000..78d7f34143 --- /dev/null +++ b/tests/detectors/cyclomatic-complexity/0.8.16/LowCyclomaticComplexity.sol @@ -0,0 +1,15 @@ +pragma solidity 0.8.16; + +contract LowCyclomaticComplexity +{ + function lowCC() public pure + { + for (uint i = 0; i < 10; i++) + { + for (uint j = 0; j < i; j++) + { + uint a = i + 1; + } + } + } +} \ No newline at end of file diff --git a/tests/detectors/cyclomatic-complexity/0.8.16/LowCyclomaticComplexity.sol.0.8.16.CyclomaticComplexity.json b/tests/detectors/cyclomatic-complexity/0.8.16/LowCyclomaticComplexity.sol.0.8.16.CyclomaticComplexity.json new file mode 100644 index 0000000000..5825bcacc6 --- /dev/null +++ b/tests/detectors/cyclomatic-complexity/0.8.16/LowCyclomaticComplexity.sol.0.8.16.CyclomaticComplexity.json @@ -0,0 +1,3 @@ +[ + [] +] \ No newline at end of file diff --git a/tests/detectors/immutable-states/0.4.25/immut_state_variables.sol b/tests/detectors/immutable-states/0.4.25/immut_state_variables.sol index 45f8cc87a1..db7f15a44c 100644 --- a/tests/detectors/immutable-states/0.4.25/immut_state_variables.sol +++ b/tests/detectors/immutable-states/0.4.25/immut_state_variables.sol @@ -45,9 +45,11 @@ contract MyConc{ uint not_constant_2 = getNumber(); uint not_constant_3 = 10 + block.number; uint not_constant_5; + string cannote_be_immutable; - constructor(uint b) public { + constructor(uint b, string memory c) public { not_constant_5 = b; + cannote_be_immutable = c; } function getNumber() public returns(uint){ diff --git a/tests/detectors/immutable-states/0.5.16/immut_state_variables.sol b/tests/detectors/immutable-states/0.5.16/immut_state_variables.sol index 8fd1ca8087..138f007b7a 100644 --- a/tests/detectors/immutable-states/0.5.16/immut_state_variables.sol +++ b/tests/detectors/immutable-states/0.5.16/immut_state_variables.sol @@ -46,9 +46,11 @@ contract MyConc{ uint not_constant_2 = getNumber(); uint not_constant_3 = 10 + block.number; uint not_constant_5; + string cannote_be_immutable; - constructor(uint b) public { + constructor(uint b, string memory c) public { not_constant_5 = b; + cannote_be_immutable = c; } function getNumber() public returns(uint){ diff --git a/tests/detectors/immutable-states/0.6.11/immut_state_variables.sol b/tests/detectors/immutable-states/0.6.11/immut_state_variables.sol index 17548f46f0..1045962905 100644 --- a/tests/detectors/immutable-states/0.6.11/immut_state_variables.sol +++ b/tests/detectors/immutable-states/0.6.11/immut_state_variables.sol @@ -67,9 +67,11 @@ contract Good { uint immutable should_be_immutable_3 = 10 + block.number; B immutable should_be_immutable_4 = new B(); uint immutable should_be_immutable_5; + string cannote_be_immutable; - constructor(uint b) public { + constructor(uint b, string memory c) public { should_be_immutable_5 = b; + cannote_be_immutable = c; } function getNumber() public returns(uint){ diff --git a/tests/detectors/immutable-states/0.7.6/immut_state_variables.sol b/tests/detectors/immutable-states/0.7.6/immut_state_variables.sol index 297dd62948..8b8e6ae95d 100644 --- a/tests/detectors/immutable-states/0.7.6/immut_state_variables.sol +++ b/tests/detectors/immutable-states/0.7.6/immut_state_variables.sol @@ -66,9 +66,11 @@ contract Good { uint immutable should_be_immutable_3 = 10 + block.number; B immutable should_be_immutable_4 = new B(); uint immutable should_be_immutable_5; + string cannote_be_immutable; - constructor(uint b) { + constructor(uint b, string memory c) { should_be_immutable_5 = b; + cannote_be_immutable = c; } function getNumber() public returns(uint){ diff --git a/tests/detectors/immutable-states/0.8.0/immut_state_variables.sol b/tests/detectors/immutable-states/0.8.0/immut_state_variables.sol index f405a15875..943b06ec32 100644 --- a/tests/detectors/immutable-states/0.8.0/immut_state_variables.sol +++ b/tests/detectors/immutable-states/0.8.0/immut_state_variables.sol @@ -44,9 +44,11 @@ contract Bad { uint should_be_immutable_2 = getNumber(); uint should_be_immutable_3 = 10 + block.number; uint should_be_immutable_5; + string cannote_be_immutable; - constructor(uint b) { + constructor(uint b, string memory c) { should_be_immutable_5 = b; + cannote_be_immutable = c; } function getNumber() public returns(uint){ diff --git a/tests/detectors/immutable-states/0.8.0/immut_state_variables.sol.0.8.0.CouldBeImmutable.json b/tests/detectors/immutable-states/0.8.0/immut_state_variables.sol.0.8.0.CouldBeImmutable.json index afa2e3bb22..58d26f9bcf 100644 --- a/tests/detectors/immutable-states/0.8.0/immut_state_variables.sol.0.8.0.CouldBeImmutable.json +++ b/tests/detectors/immutable-states/0.8.0/immut_state_variables.sol.0.8.0.CouldBeImmutable.json @@ -24,7 +24,7 @@ "name": "Bad", "source_mapping": { "start": 718, - "length": 493, + "length": 577, "filename_relative": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", @@ -49,7 +49,9 @@ 53, 54, 55, - 56 + 56, + 57, + 58 ], "starting_column": 1, "ending_column": 2 @@ -90,7 +92,7 @@ "name": "Bad", "source_mapping": { "start": 718, - "length": 493, + "length": 577, "filename_relative": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", @@ -115,7 +117,9 @@ 53, 54, 55, - 56 + 56, + 57, + 58 ], "starting_column": 1, "ending_column": 2 @@ -156,7 +160,7 @@ "name": "Bad", "source_mapping": { "start": 718, - "length": 493, + "length": 577, "filename_relative": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", @@ -181,7 +185,9 @@ 53, 54, 55, - 56 + 56, + 57, + 58 ], "starting_column": 1, "ending_column": 2 @@ -222,7 +228,7 @@ "name": "Bad", "source_mapping": { "start": 718, - "length": 493, + "length": 577, "filename_relative": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", "filename_absolute": "/GENERIC_PATH", "filename_short": "tests/detectors/immutable-states/0.8.0/immut_state_variables.sol", @@ -247,7 +253,9 @@ 53, 54, 55, - 56 + 56, + 57, + 58 ], "starting_column": 1, "ending_column": 2 diff --git a/tests/detectors/naming-convention/0.4.25/no_warning_for_public_constants.sol b/tests/detectors/naming-convention/0.4.25/no_warning_for_public_constants.sol new file mode 100644 index 0000000000..0c1da4c26c --- /dev/null +++ b/tests/detectors/naming-convention/0.4.25/no_warning_for_public_constants.sol @@ -0,0 +1,4 @@ +contract A +{ + uint256 public constant myVal = 0; +} \ No newline at end of file diff --git a/tests/detectors/naming-convention/0.4.25/no_warning_for_public_constants.sol.0.4.25.NamingConvention.json b/tests/detectors/naming-convention/0.4.25/no_warning_for_public_constants.sol.0.4.25.NamingConvention.json new file mode 100644 index 0000000000..5825bcacc6 --- /dev/null +++ b/tests/detectors/naming-convention/0.4.25/no_warning_for_public_constants.sol.0.4.25.NamingConvention.json @@ -0,0 +1,3 @@ +[ + [] +] \ No newline at end of file diff --git a/tests/detectors/naming-convention/0.5.16/no_warning_for_public_constants.sol b/tests/detectors/naming-convention/0.5.16/no_warning_for_public_constants.sol new file mode 100644 index 0000000000..0c1da4c26c --- /dev/null +++ b/tests/detectors/naming-convention/0.5.16/no_warning_for_public_constants.sol @@ -0,0 +1,4 @@ +contract A +{ + uint256 public constant myVal = 0; +} \ No newline at end of file diff --git a/tests/detectors/naming-convention/0.5.16/no_warning_for_public_constants.sol.0.5.16.NamingConvention.json b/tests/detectors/naming-convention/0.5.16/no_warning_for_public_constants.sol.0.5.16.NamingConvention.json new file mode 100644 index 0000000000..5825bcacc6 --- /dev/null +++ b/tests/detectors/naming-convention/0.5.16/no_warning_for_public_constants.sol.0.5.16.NamingConvention.json @@ -0,0 +1,3 @@ +[ + [] +] \ No newline at end of file diff --git a/tests/detectors/naming-convention/0.6.11/no_warning_for_public_constants.sol b/tests/detectors/naming-convention/0.6.11/no_warning_for_public_constants.sol new file mode 100644 index 0000000000..0c1da4c26c --- /dev/null +++ b/tests/detectors/naming-convention/0.6.11/no_warning_for_public_constants.sol @@ -0,0 +1,4 @@ +contract A +{ + uint256 public constant myVal = 0; +} \ No newline at end of file diff --git a/tests/detectors/naming-convention/0.6.11/no_warning_for_public_constants.sol.0.6.11.NamingConvention.json b/tests/detectors/naming-convention/0.6.11/no_warning_for_public_constants.sol.0.6.11.NamingConvention.json new file mode 100644 index 0000000000..5825bcacc6 --- /dev/null +++ b/tests/detectors/naming-convention/0.6.11/no_warning_for_public_constants.sol.0.6.11.NamingConvention.json @@ -0,0 +1,3 @@ +[ + [] +] \ No newline at end of file diff --git a/tests/detectors/naming-convention/0.7.6/no_warning_for_public_constants.sol b/tests/detectors/naming-convention/0.7.6/no_warning_for_public_constants.sol new file mode 100644 index 0000000000..e5371cbe32 --- /dev/null +++ b/tests/detectors/naming-convention/0.7.6/no_warning_for_public_constants.sol @@ -0,0 +1,4 @@ +contract A +{ + uint256 public constant myVal = 0; +} diff --git a/tests/detectors/naming-convention/0.7.6/no_warning_for_public_constants.sol.0.7.6.NamingConvention.json b/tests/detectors/naming-convention/0.7.6/no_warning_for_public_constants.sol.0.7.6.NamingConvention.json new file mode 100644 index 0000000000..5825bcacc6 --- /dev/null +++ b/tests/detectors/naming-convention/0.7.6/no_warning_for_public_constants.sol.0.7.6.NamingConvention.json @@ -0,0 +1,3 @@ +[ + [] +] \ No newline at end of file diff --git a/tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol b/tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol index b67833f05f..acd93bb46a 100644 --- a/tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol +++ b/tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol @@ -24,3 +24,13 @@ contract FurtherExtendedContract is ExtendedContract { function shadowingParent(uint x) public pure { int y; uint z; uint w; uint v; } } + +contract LocalReturnVariables { + uint state; + function shadowedState() external view returns(uint state) { + return state; + } + function good() external view returns(uint val1) { + return val1; + } +} \ No newline at end of file diff --git a/tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol.0.4.25.LocalShadowing.json b/tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol.0.4.25.LocalShadowing.json index ce0f062a63..e12b5f26df 100644 --- a/tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol.0.4.25.LocalShadowing.json +++ b/tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol.0.4.25.LocalShadowing.json @@ -204,6 +204,129 @@ "impact": "Low", "confidence": "High" }, + { + "elements": [ + { + "type": "variable", + "name": "state", + "source_mapping": { + "start": 533, + "length": 10, + "filename_relative": "tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 30 + ], + "starting_column": 52, + "ending_column": 62 + }, + "type_specific_fields": { + "parent": { + "type": "function", + "name": "shadowedState", + "source_mapping": { + "start": 486, + "length": 88, + "filename_relative": "tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 30, + 31, + 32 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "LocalReturnVariables", + "source_mapping": { + "start": 434, + "length": 225, + "filename_relative": "tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37 + ], + "starting_column": 1, + "ending_column": 0 + } + }, + "signature": "shadowedState()" + } + } + } + }, + { + "type": "variable", + "name": "state", + "source_mapping": { + "start": 470, + "length": 10, + "filename_relative": "tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 29 + ], + "starting_column": 5, + "ending_column": 15 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "LocalReturnVariables", + "source_mapping": { + "start": 434, + "length": 225, + "filename_relative": "tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37 + ], + "starting_column": 1, + "ending_column": 0 + } + } + } + } + ], + "description": "LocalReturnVariables.shadowedState().state (tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol#30) shadows:\n\t- LocalReturnVariables.state (tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol#29) (state variable)\n", + "markdown": "[LocalReturnVariables.shadowedState().state](tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol#L30) shadows:\n\t- [LocalReturnVariables.state](tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol#L29) (state variable)\n", + "first_markdown_element": "tests/detectors/shadowing-local/0.4.25/shadowing_local_variable.sol#L30", + "id": "1b0030affabcff703e57e4f388b86dbda0f412e51ba8d15248bcae9e4748a012", + "check": "shadowing-local", + "impact": "Low", + "confidence": "High" + }, { "elements": [ { diff --git a/tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol b/tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol index ec17297e62..385f6194c2 100644 --- a/tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol +++ b/tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol @@ -24,3 +24,17 @@ contract FurtherExtendedContract is ExtendedContract { function shadowingParent(uint x) public pure { int y; uint z; uint w; uint v; } } + +contract LocalReturnVariables { + uint state; + function shadowedState() external view returns(uint state) { + return state; + } + function shadowedReturn() external view returns(uint local) { + uint local = 1; + return local; + } + function good() external view returns(uint val1) { + return val1; + } +} \ No newline at end of file diff --git a/tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol.0.5.16.LocalShadowing.json b/tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol.0.5.16.LocalShadowing.json index 1385b0e98d..d4d54ad9d9 100644 --- a/tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol.0.5.16.LocalShadowing.json +++ b/tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol.0.5.16.LocalShadowing.json @@ -204,6 +204,137 @@ "impact": "Low", "confidence": "High" }, + { + "elements": [ + { + "type": "variable", + "name": "state", + "source_mapping": { + "start": 536, + "length": 10, + "filename_relative": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 30 + ], + "starting_column": 52, + "ending_column": 62 + }, + "type_specific_fields": { + "parent": { + "type": "function", + "name": "shadowedState", + "source_mapping": { + "start": 489, + "length": 88, + "filename_relative": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 30, + 31, + 32 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "LocalReturnVariables", + "source_mapping": { + "start": 437, + "length": 344, + "filename_relative": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41 + ], + "starting_column": 1, + "ending_column": 0 + } + }, + "signature": "shadowedState()" + } + } + } + }, + { + "type": "variable", + "name": "state", + "source_mapping": { + "start": 473, + "length": 10, + "filename_relative": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 29 + ], + "starting_column": 5, + "ending_column": 15 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "LocalReturnVariables", + "source_mapping": { + "start": 437, + "length": 344, + "filename_relative": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41 + ], + "starting_column": 1, + "ending_column": 0 + } + } + } + } + ], + "description": "LocalReturnVariables.shadowedState().state (tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol#30) shadows:\n\t- LocalReturnVariables.state (tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol#29) (state variable)\n", + "markdown": "[LocalReturnVariables.shadowedState().state](tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol#L30) shadows:\n\t- [LocalReturnVariables.state](tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol#L29) (state variable)\n", + "first_markdown_element": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol#L30", + "id": "1b0030affabcff703e57e4f388b86dbda0f412e51ba8d15248bcae9e4748a012", + "check": "shadowing-local", + "impact": "Low", + "confidence": "High" + }, { "elements": [ { @@ -567,6 +698,161 @@ "impact": "Low", "confidence": "High" }, + { + "elements": [ + { + "type": "variable", + "name": "local_scope_0", + "source_mapping": { + "start": 653, + "length": 14, + "filename_relative": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 34 + ], + "starting_column": 9, + "ending_column": 23 + }, + "type_specific_fields": { + "parent": { + "type": "function", + "name": "shadowedReturn", + "source_mapping": { + "start": 583, + "length": 113, + "filename_relative": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 33, + 34, + 35, + 36 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "LocalReturnVariables", + "source_mapping": { + "start": 437, + "length": 344, + "filename_relative": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41 + ], + "starting_column": 1, + "ending_column": 0 + } + }, + "signature": "shadowedReturn()" + } + } + } + }, + { + "type": "variable", + "name": "local", + "source_mapping": { + "start": 631, + "length": 10, + "filename_relative": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 33 + ], + "starting_column": 53, + "ending_column": 63 + }, + "type_specific_fields": { + "parent": { + "type": "function", + "name": "shadowedReturn", + "source_mapping": { + "start": 583, + "length": 113, + "filename_relative": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 33, + 34, + 35, + 36 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "LocalReturnVariables", + "source_mapping": { + "start": 437, + "length": 344, + "filename_relative": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41 + ], + "starting_column": 1, + "ending_column": 0 + } + }, + "signature": "shadowedReturn()" + } + } + } + } + ], + "description": "LocalReturnVariables.shadowedReturn().local_scope_0 (tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol#34) shadows:\n\t- LocalReturnVariables.shadowedReturn().local (tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol#33) (return variable)\n", + "markdown": "[LocalReturnVariables.shadowedReturn().local_scope_0](tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol#L34) shadows:\n\t- [LocalReturnVariables.shadowedReturn().local](tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol#L33) (return variable)\n", + "first_markdown_element": "tests/detectors/shadowing-local/0.5.16/shadowing_local_variable.sol#L34", + "id": "cd63bdf3f6420e4e109d20ec44b52fcbcbde1c5b6a0701fc6994b35960ab1e85", + "check": "shadowing-local", + "impact": "Low", + "confidence": "High" + }, { "elements": [ { diff --git a/tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol b/tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol index 450dc851be..c44fe934e4 100644 --- a/tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol +++ b/tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol @@ -24,3 +24,17 @@ contract FurtherExtendedContract is ExtendedContract { function shadowingParent(uint __x) public pure { int y; uint z; uint w; uint v; } } + +contract LocalReturnVariables { + uint state; + function shadowedState() external view returns(uint state) { + return state; + } + function shadowedReturn() external view returns(uint local) { + uint local = 1; + return local; + } + function good() external view returns(uint val1) { + return val1; + } +} \ No newline at end of file diff --git a/tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol.0.6.11.LocalShadowing.json b/tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol.0.6.11.LocalShadowing.json index 0c495f07c3..2dc747505d 100644 --- a/tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol.0.6.11.LocalShadowing.json +++ b/tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol.0.6.11.LocalShadowing.json @@ -1,5 +1,136 @@ [ [ + { + "elements": [ + { + "type": "variable", + "name": "state", + "source_mapping": { + "start": 541, + "length": 10, + "filename_relative": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 30 + ], + "starting_column": 52, + "ending_column": 62 + }, + "type_specific_fields": { + "parent": { + "type": "function", + "name": "shadowedState", + "source_mapping": { + "start": 494, + "length": 88, + "filename_relative": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 30, + 31, + 32 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "LocalReturnVariables", + "source_mapping": { + "start": 442, + "length": 344, + "filename_relative": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41 + ], + "starting_column": 1, + "ending_column": 0 + } + }, + "signature": "shadowedState()" + } + } + } + }, + { + "type": "variable", + "name": "state", + "source_mapping": { + "start": 478, + "length": 10, + "filename_relative": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 29 + ], + "starting_column": 5, + "ending_column": 15 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "LocalReturnVariables", + "source_mapping": { + "start": 442, + "length": 344, + "filename_relative": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41 + ], + "starting_column": 1, + "ending_column": 0 + } + } + } + } + ], + "description": "LocalReturnVariables.shadowedState().state (tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol#30) shadows:\n\t- LocalReturnVariables.state (tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol#29) (state variable)\n", + "markdown": "[LocalReturnVariables.shadowedState().state](tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol#L30) shadows:\n\t- [LocalReturnVariables.state](tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol#L29) (state variable)\n", + "first_markdown_element": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol#L30", + "id": "1b0030affabcff703e57e4f388b86dbda0f412e51ba8d15248bcae9e4748a012", + "check": "shadowing-local", + "impact": "Low", + "confidence": "High" + }, { "elements": [ { @@ -486,6 +617,161 @@ "impact": "Low", "confidence": "High" }, + { + "elements": [ + { + "type": "variable", + "name": "local_scope_0", + "source_mapping": { + "start": 658, + "length": 14, + "filename_relative": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 34 + ], + "starting_column": 9, + "ending_column": 23 + }, + "type_specific_fields": { + "parent": { + "type": "function", + "name": "shadowedReturn", + "source_mapping": { + "start": 588, + "length": 113, + "filename_relative": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 33, + 34, + 35, + 36 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "LocalReturnVariables", + "source_mapping": { + "start": 442, + "length": 344, + "filename_relative": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41 + ], + "starting_column": 1, + "ending_column": 0 + } + }, + "signature": "shadowedReturn()" + } + } + } + }, + { + "type": "variable", + "name": "local", + "source_mapping": { + "start": 636, + "length": 10, + "filename_relative": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 33 + ], + "starting_column": 53, + "ending_column": 63 + }, + "type_specific_fields": { + "parent": { + "type": "function", + "name": "shadowedReturn", + "source_mapping": { + "start": 588, + "length": 113, + "filename_relative": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 33, + 34, + 35, + 36 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "LocalReturnVariables", + "source_mapping": { + "start": 442, + "length": 344, + "filename_relative": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41 + ], + "starting_column": 1, + "ending_column": 0 + } + }, + "signature": "shadowedReturn()" + } + } + } + } + ], + "description": "LocalReturnVariables.shadowedReturn().local_scope_0 (tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol#34) shadows:\n\t- LocalReturnVariables.shadowedReturn().local (tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol#33) (return variable)\n", + "markdown": "[LocalReturnVariables.shadowedReturn().local_scope_0](tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol#L34) shadows:\n\t- [LocalReturnVariables.shadowedReturn().local](tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol#L33) (return variable)\n", + "first_markdown_element": "tests/detectors/shadowing-local/0.6.11/shadowing_local_variable.sol#L34", + "id": "cd63bdf3f6420e4e109d20ec44b52fcbcbde1c5b6a0701fc6994b35960ab1e85", + "check": "shadowing-local", + "impact": "Low", + "confidence": "High" + }, { "elements": [ { diff --git a/tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol b/tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol index 450dc851be..e43477e540 100644 --- a/tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol +++ b/tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol @@ -24,3 +24,17 @@ contract FurtherExtendedContract is ExtendedContract { function shadowingParent(uint __x) public pure { int y; uint z; uint w; uint v; } } + +contract LocalReturnVariables { + uint state; + function shadowedState() external view returns(uint state) { + return state; + } + function shadowedReturn() external view returns(uint local) { + uint local = 1; + return local; + } + function good() external view returns(uint val1) { + return val1; + } +} diff --git a/tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol.0.7.6.LocalShadowing.json b/tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol.0.7.6.LocalShadowing.json index 9ea9783e13..e013dc0eb4 100644 --- a/tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol.0.7.6.LocalShadowing.json +++ b/tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol.0.7.6.LocalShadowing.json @@ -1,5 +1,134 @@ [ [ + { + "elements": [ + { + "type": "variable", + "name": "state", + "source_mapping": { + "start": 541, + "length": 10, + "filename_relative": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 30 + ], + "starting_column": 52, + "ending_column": 62 + }, + "type_specific_fields": { + "parent": { + "type": "function", + "name": "shadowedState", + "source_mapping": { + "start": 494, + "length": 88, + "filename_relative": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 30, + 31, + 32 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "LocalReturnVariables", + "source_mapping": { + "start": 442, + "length": 344, + "filename_relative": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "shadowedState()" + } + } + } + }, + { + "type": "variable", + "name": "state", + "source_mapping": { + "start": 478, + "length": 10, + "filename_relative": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 29 + ], + "starting_column": 5, + "ending_column": 15 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "LocalReturnVariables", + "source_mapping": { + "start": 442, + "length": 344, + "filename_relative": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40 + ], + "starting_column": 1, + "ending_column": 2 + } + } + } + } + ], + "description": "LocalReturnVariables.shadowedState().state (tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol#30) shadows:\n\t- LocalReturnVariables.state (tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol#29) (state variable)\n", + "markdown": "[LocalReturnVariables.shadowedState().state](tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol#L30) shadows:\n\t- [LocalReturnVariables.state](tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol#L29) (state variable)\n", + "first_markdown_element": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol#L30", + "id": "1b0030affabcff703e57e4f388b86dbda0f412e51ba8d15248bcae9e4748a012", + "check": "shadowing-local", + "impact": "Low", + "confidence": "High" + }, { "elements": [ { @@ -486,6 +615,159 @@ "impact": "Low", "confidence": "High" }, + { + "elements": [ + { + "type": "variable", + "name": "local_scope_0", + "source_mapping": { + "start": 658, + "length": 14, + "filename_relative": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 34 + ], + "starting_column": 9, + "ending_column": 23 + }, + "type_specific_fields": { + "parent": { + "type": "function", + "name": "shadowedReturn", + "source_mapping": { + "start": 588, + "length": 113, + "filename_relative": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 33, + 34, + 35, + 36 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "LocalReturnVariables", + "source_mapping": { + "start": 442, + "length": 344, + "filename_relative": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "shadowedReturn()" + } + } + } + }, + { + "type": "variable", + "name": "local", + "source_mapping": { + "start": 636, + "length": 10, + "filename_relative": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 33 + ], + "starting_column": 53, + "ending_column": 63 + }, + "type_specific_fields": { + "parent": { + "type": "function", + "name": "shadowedReturn", + "source_mapping": { + "start": 588, + "length": 113, + "filename_relative": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 33, + 34, + 35, + 36 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "LocalReturnVariables", + "source_mapping": { + "start": 442, + "length": 344, + "filename_relative": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol", + "is_dependency": false, + "lines": [ + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "shadowedReturn()" + } + } + } + } + ], + "description": "LocalReturnVariables.shadowedReturn().local_scope_0 (tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol#34) shadows:\n\t- LocalReturnVariables.shadowedReturn().local (tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol#33) (return variable)\n", + "markdown": "[LocalReturnVariables.shadowedReturn().local_scope_0](tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol#L34) shadows:\n\t- [LocalReturnVariables.shadowedReturn().local](tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol#L33) (return variable)\n", + "first_markdown_element": "tests/detectors/shadowing-local/0.7.6/shadowing_local_variable.sol#L34", + "id": "cd63bdf3f6420e4e109d20ec44b52fcbcbde1c5b6a0701fc6994b35960ab1e85", + "check": "shadowing-local", + "impact": "Low", + "confidence": "High" + }, { "elements": [ { diff --git a/tests/detectors/solc-version/0.5.16/dynamic_1.sol.0.5.16.IncorrectSolc.json b/tests/detectors/solc-version/0.5.16/dynamic_1.sol.0.5.16.IncorrectSolc.json index 28381e3cae..599392dc8b 100644 --- a/tests/detectors/solc-version/0.5.16/dynamic_1.sol.0.5.16.IncorrectSolc.json +++ b/tests/detectors/solc-version/0.5.16/dynamic_1.sol.0.5.16.IncorrectSolc.json @@ -1,5 +1,15 @@ [ [ + { + "elements": [], + "description": "solc-0.5.16 is not recommended for deployment\n", + "markdown": "solc-0.5.16 is not recommended for deployment\n", + "first_markdown_element": "", + "id": "94ddf430efb860e471a768a108c851848fa998e8a2c489c6fb23ed71d3ef4b09", + "check": "solc-version", + "impact": "Informational", + "confidence": "High" + }, { "elements": [ { diff --git a/tests/detectors/solc-version/0.5.16/dynamic_2.sol.0.5.16.IncorrectSolc.json b/tests/detectors/solc-version/0.5.16/dynamic_2.sol.0.5.16.IncorrectSolc.json index 19ab5889f8..8eacb56636 100644 --- a/tests/detectors/solc-version/0.5.16/dynamic_2.sol.0.5.16.IncorrectSolc.json +++ b/tests/detectors/solc-version/0.5.16/dynamic_2.sol.0.5.16.IncorrectSolc.json @@ -38,6 +38,16 @@ "check": "solc-version", "impact": "Informational", "confidence": "High" + }, + { + "elements": [], + "description": "solc-0.5.16 is not recommended for deployment\n", + "markdown": "solc-0.5.16 is not recommended for deployment\n", + "first_markdown_element": "", + "id": "94ddf430efb860e471a768a108c851848fa998e8a2c489c6fb23ed71d3ef4b09", + "check": "solc-version", + "impact": "Informational", + "confidence": "High" } ] ] \ No newline at end of file diff --git a/tests/detectors/solc-version/0.5.16/static.sol.0.5.16.IncorrectSolc.json b/tests/detectors/solc-version/0.5.16/static.sol.0.5.16.IncorrectSolc.json index 5825bcacc6..df4bac19d6 100644 --- a/tests/detectors/solc-version/0.5.16/static.sol.0.5.16.IncorrectSolc.json +++ b/tests/detectors/solc-version/0.5.16/static.sol.0.5.16.IncorrectSolc.json @@ -1,3 +1,49 @@ [ - [] + [ + { + "elements": [ + { + "type": "pragma", + "name": "0.5.16", + "source_mapping": { + "start": 0, + "length": 23, + "filename_relative": "tests/detectors/solc-version/0.5.16/static.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/solc-version/0.5.16/static.sol", + "is_dependency": false, + "lines": [ + 1 + ], + "starting_column": 1, + "ending_column": 24 + }, + "type_specific_fields": { + "directive": [ + "solidity", + "0.5", + ".16" + ] + } + } + ], + "description": "Pragma version0.5.16 (tests/detectors/solc-version/0.5.16/static.sol#1) allows old versions\n", + "markdown": "Pragma version[0.5.16](tests/detectors/solc-version/0.5.16/static.sol#L1) allows old versions\n", + "first_markdown_element": "tests/detectors/solc-version/0.5.16/static.sol#L1", + "id": "2407d991de90e57d2f6b6bdbc61bb939845a5c0bb2d82910ed4c49abff2ab6e3", + "check": "solc-version", + "impact": "Informational", + "confidence": "High" + }, + { + "elements": [], + "description": "solc-0.5.16 is not recommended for deployment\n", + "markdown": "solc-0.5.16 is not recommended for deployment\n", + "first_markdown_element": "", + "id": "94ddf430efb860e471a768a108c851848fa998e8a2c489c6fb23ed71d3ef4b09", + "check": "solc-version", + "impact": "Informational", + "confidence": "High" + } + ] ] \ No newline at end of file diff --git a/tests/detectors/solc-version/0.6.11/dynamic_1.sol.0.6.11.IncorrectSolc.json b/tests/detectors/solc-version/0.6.11/dynamic_1.sol.0.6.11.IncorrectSolc.json index b182c02c70..a71fa64067 100644 --- a/tests/detectors/solc-version/0.6.11/dynamic_1.sol.0.6.11.IncorrectSolc.json +++ b/tests/detectors/solc-version/0.6.11/dynamic_1.sol.0.6.11.IncorrectSolc.json @@ -35,6 +35,16 @@ "check": "solc-version", "impact": "Informational", "confidence": "High" + }, + { + "elements": [], + "description": "solc-0.6.11 is not recommended for deployment\n", + "markdown": "solc-0.6.11 is not recommended for deployment\n", + "first_markdown_element": "", + "id": "bafd522d637977886f038e619ad47c1987efedc6c4c24515e6e27b23585535bd", + "check": "solc-version", + "impact": "Informational", + "confidence": "High" } ] ] \ No newline at end of file diff --git a/tests/detectors/solc-version/0.6.11/dynamic_2.sol.0.6.11.IncorrectSolc.json b/tests/detectors/solc-version/0.6.11/dynamic_2.sol.0.6.11.IncorrectSolc.json index 4f844e25ee..d6cac106b2 100644 --- a/tests/detectors/solc-version/0.6.11/dynamic_2.sol.0.6.11.IncorrectSolc.json +++ b/tests/detectors/solc-version/0.6.11/dynamic_2.sol.0.6.11.IncorrectSolc.json @@ -38,6 +38,16 @@ "check": "solc-version", "impact": "Informational", "confidence": "High" + }, + { + "elements": [], + "description": "solc-0.6.11 is not recommended for deployment\n", + "markdown": "solc-0.6.11 is not recommended for deployment\n", + "first_markdown_element": "", + "id": "bafd522d637977886f038e619ad47c1987efedc6c4c24515e6e27b23585535bd", + "check": "solc-version", + "impact": "Informational", + "confidence": "High" } ] ] \ No newline at end of file diff --git a/tests/detectors/solc-version/0.6.11/static.sol.0.6.11.IncorrectSolc.json b/tests/detectors/solc-version/0.6.11/static.sol.0.6.11.IncorrectSolc.json index 5825bcacc6..61ea21e160 100644 --- a/tests/detectors/solc-version/0.6.11/static.sol.0.6.11.IncorrectSolc.json +++ b/tests/detectors/solc-version/0.6.11/static.sol.0.6.11.IncorrectSolc.json @@ -1,3 +1,49 @@ [ - [] + [ + { + "elements": [ + { + "type": "pragma", + "name": "0.6.11", + "source_mapping": { + "start": 0, + "length": 23, + "filename_relative": "tests/detectors/solc-version/0.6.11/static.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/solc-version/0.6.11/static.sol", + "is_dependency": false, + "lines": [ + 1 + ], + "starting_column": 1, + "ending_column": 24 + }, + "type_specific_fields": { + "directive": [ + "solidity", + "0.6", + ".11" + ] + } + } + ], + "description": "Pragma version0.6.11 (tests/detectors/solc-version/0.6.11/static.sol#1) allows old versions\n", + "markdown": "Pragma version[0.6.11](tests/detectors/solc-version/0.6.11/static.sol#L1) allows old versions\n", + "first_markdown_element": "tests/detectors/solc-version/0.6.11/static.sol#L1", + "id": "ad7b24eed22ac098a57ae02ade0ccffb4cb094e851effe93cad1d0a65b489816", + "check": "solc-version", + "impact": "Informational", + "confidence": "High" + }, + { + "elements": [], + "description": "solc-0.6.11 is not recommended for deployment\n", + "markdown": "solc-0.6.11 is not recommended for deployment\n", + "first_markdown_element": "", + "id": "bafd522d637977886f038e619ad47c1987efedc6c4c24515e6e27b23585535bd", + "check": "solc-version", + "impact": "Informational", + "confidence": "High" + } + ] ] \ No newline at end of file diff --git a/tests/detectors/solc-version/0.7.6/dynamic_1.sol.0.7.6.IncorrectSolc.json b/tests/detectors/solc-version/0.7.6/dynamic_1.sol.0.7.6.IncorrectSolc.json index aafb7b5d18..7633024457 100644 --- a/tests/detectors/solc-version/0.7.6/dynamic_1.sol.0.7.6.IncorrectSolc.json +++ b/tests/detectors/solc-version/0.7.6/dynamic_1.sol.0.7.6.IncorrectSolc.json @@ -1,5 +1,15 @@ [ [ + { + "elements": [], + "description": "solc-0.7.6 is not recommended for deployment\n", + "markdown": "solc-0.7.6 is not recommended for deployment\n", + "first_markdown_element": "", + "id": "ddb8ee36d9dd69b14eab702506268f8f9ef3283777d042e197277e29407b386e", + "check": "solc-version", + "impact": "Informational", + "confidence": "High" + }, { "elements": [ { diff --git a/tests/detectors/solc-version/0.7.6/dynamic_2.sol.0.7.6.IncorrectSolc.json b/tests/detectors/solc-version/0.7.6/dynamic_2.sol.0.7.6.IncorrectSolc.json index 4aea327634..516f3142b3 100644 --- a/tests/detectors/solc-version/0.7.6/dynamic_2.sol.0.7.6.IncorrectSolc.json +++ b/tests/detectors/solc-version/0.7.6/dynamic_2.sol.0.7.6.IncorrectSolc.json @@ -38,6 +38,16 @@ "check": "solc-version", "impact": "Informational", "confidence": "High" + }, + { + "elements": [], + "description": "solc-0.7.6 is not recommended for deployment\n", + "markdown": "solc-0.7.6 is not recommended for deployment\n", + "first_markdown_element": "", + "id": "ddb8ee36d9dd69b14eab702506268f8f9ef3283777d042e197277e29407b386e", + "check": "solc-version", + "impact": "Informational", + "confidence": "High" } ] ] \ No newline at end of file diff --git a/tests/detectors/solc-version/0.7.6/static.sol.0.7.6.IncorrectSolc.json b/tests/detectors/solc-version/0.7.6/static.sol.0.7.6.IncorrectSolc.json index 5825bcacc6..426786fffe 100644 --- a/tests/detectors/solc-version/0.7.6/static.sol.0.7.6.IncorrectSolc.json +++ b/tests/detectors/solc-version/0.7.6/static.sol.0.7.6.IncorrectSolc.json @@ -1,3 +1,49 @@ [ - [] + [ + { + "elements": [], + "description": "solc-0.7.6 is not recommended for deployment\n", + "markdown": "solc-0.7.6 is not recommended for deployment\n", + "first_markdown_element": "", + "id": "ddb8ee36d9dd69b14eab702506268f8f9ef3283777d042e197277e29407b386e", + "check": "solc-version", + "impact": "Informational", + "confidence": "High" + }, + { + "elements": [ + { + "type": "pragma", + "name": "0.7.6", + "source_mapping": { + "start": 0, + "length": 22, + "filename_relative": "tests/detectors/solc-version/0.7.6/static.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/solc-version/0.7.6/static.sol", + "is_dependency": false, + "lines": [ + 1 + ], + "starting_column": 1, + "ending_column": 23 + }, + "type_specific_fields": { + "directive": [ + "solidity", + "0.7", + ".6" + ] + } + } + ], + "description": "Pragma version0.7.6 (tests/detectors/solc-version/0.7.6/static.sol#1) allows old versions\n", + "markdown": "Pragma version[0.7.6](tests/detectors/solc-version/0.7.6/static.sol#L1) allows old versions\n", + "first_markdown_element": "tests/detectors/solc-version/0.7.6/static.sol#L1", + "id": "e7a5c0ee3d0af7a59907908f88499f79435e302745284c6279167a1cc209b4ed", + "check": "solc-version", + "impact": "Informational", + "confidence": "High" + } + ] ] \ No newline at end of file diff --git a/tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol b/tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol new file mode 100644 index 0000000000..a2fb6a77c2 --- /dev/null +++ b/tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol @@ -0,0 +1,17 @@ +contract Uninitialized{ + + struct St{ + uint a; + } + + function bad() internal returns (St storage ret){ + ret = ret; + ret.a += 1; + } + + function ok(St storage ret) internal { + ret = ret; + ret.a += 1; + } + +} diff --git a/tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol.0.8.19.UninitializedStorageVars.json b/tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol.0.8.19.UninitializedStorageVars.json new file mode 100644 index 0000000000..e7fab681d3 --- /dev/null +++ b/tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol.0.8.19.UninitializedStorageVars.json @@ -0,0 +1,90 @@ +[ + [ + { + "elements": [ + { + "type": "variable", + "name": "ret", + "source_mapping": { + "start": 100, + "length": 14, + "filename_relative": "tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol", + "is_dependency": false, + "lines": [ + 7 + ], + "starting_column": 38, + "ending_column": 52 + }, + "type_specific_fields": { + "parent": { + "type": "function", + "name": "bad", + "source_mapping": { + "start": 67, + "length": 95, + "filename_relative": "tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol", + "is_dependency": false, + "lines": [ + 7, + 8, + 9, + 10 + ], + "starting_column": 5, + "ending_column": 6 + }, + "type_specific_fields": { + "parent": { + "type": "contract", + "name": "Uninitialized", + "source_mapping": { + "start": 0, + "length": 262, + "filename_relative": "tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol", + "filename_absolute": "/GENERIC_PATH", + "filename_short": "tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol", + "is_dependency": false, + "lines": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17 + ], + "starting_column": 1, + "ending_column": 2 + } + }, + "signature": "bad()" + } + } + } + } + ], + "description": "Uninitialized.bad().ret (tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol#7) is a storage variable never initialized\n", + "markdown": "[Uninitialized.bad().ret](tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol#L7) is a storage variable never initialized\n", + "first_markdown_element": "tests/detectors/uninitialized-storage/0.8.19/uninitialized_storage_pointer.sol#L7", + "id": "979d28e501693ed7ece0d429e7c30266f8e9d6a2e2eedc87006c4bad63e78706", + "check": "uninitialized-storage", + "impact": "High", + "confidence": "High" + } + ] +] \ No newline at end of file diff --git a/tests/function_features/abstract.sol b/tests/function_features/abstract.sol new file mode 100644 index 0000000000..b29f683c47 --- /dev/null +++ b/tests/function_features/abstract.sol @@ -0,0 +1,5 @@ +pragma solidity ^0.8.0; + +abstract contract ExplicitAbstract{ + function f() virtual public; +} diff --git a/tests/function_features/implicit_abstract.sol b/tests/function_features/implicit_abstract.sol new file mode 100644 index 0000000000..c46ccd8214 --- /dev/null +++ b/tests/function_features/implicit_abstract.sol @@ -0,0 +1,5 @@ +pragma solidity ^0.5.0; + +contract ImplicitAbstract{ + function f() public; +} \ No newline at end of file diff --git a/tests/slithir/operation_reads.sol b/tests/slithir/operation_reads.sol new file mode 100644 index 0000000000..22adc22889 --- /dev/null +++ b/tests/slithir/operation_reads.sol @@ -0,0 +1,17 @@ + +contract Placeholder { + constructor() payable {} +} + +contract NewContract { + bytes32 internal constant state_variable_read = bytes32(0); + + function readAllStateVariables() external { + new Placeholder{salt: state_variable_read} (); + } + + function readAllLocalVariables() external { + bytes32 local_variable_read = bytes32(0); + new Placeholder{salt: local_variable_read} (); + } +} \ No newline at end of file diff --git a/tests/slithir/ternary_expressions.sol b/tests/slithir/ternary_expressions.sol index 89fdc59d1e..c73a2b6b33 100644 --- a/tests/slithir/ternary_expressions.sol +++ b/tests/slithir/ternary_expressions.sol @@ -39,4 +39,14 @@ contract C { function g(address one) public { (, uint x) = Test(one).testTuple(); } + + uint[] myIntegers; + function _h(uint c) internal returns(uint) { + return c; + } + function h(bool cond, uint a, uint b) public { + uint d = _h( + myIntegers[cond ? a : b] + ); + } } diff --git a/tests/slithir/test_operation_reads.py b/tests/slithir/test_operation_reads.py new file mode 100644 index 0000000000..aa183333fd --- /dev/null +++ b/tests/slithir/test_operation_reads.py @@ -0,0 +1,49 @@ +from collections import namedtuple +from slither import Slither +from slither.slithir.operations import Operation, NewContract + + +def check_num_local_vars_read(function, slithir_op: Operation, num_reads_expected: int): + for node in function.nodes: + for operation in node.irs: + if isinstance(operation, slithir_op): + assert len(operation.read) == num_reads_expected + assert len(node.local_variables_read) == num_reads_expected + + +def check_num_states_vars_read(function, slithir_op: Operation, num_reads_expected: int): + for node in function.nodes: + for operation in node.irs: + if isinstance(operation, slithir_op): + assert len(operation.read) == num_reads_expected + assert len(node.state_variables_read) == num_reads_expected + + +OperationTest = namedtuple("OperationTest", "contract_name slithir_op") + +OPERATION_TEST = [OperationTest("NewContract", NewContract)] + + +def test_operation_reads() -> None: + """ + Every slithir operation has its own contract and reads all local and state variables in readAllLocalVariables and readAllStateVariables, respectively. + """ + slither = Slither("./tests/slithir/operation_reads.sol") + + for op_test in OPERATION_TEST: + print(op_test) + available = slither.get_contract_from_name(op_test.contract_name) + assert len(available) == 1 + target = available[0] + + num_state_variables = len(target.state_variables_ordered) + state_function = target.get_function_from_signature("readAllStateVariables()") + check_num_states_vars_read(state_function, op_test.slithir_op, num_state_variables) + + local_function = target.get_function_from_signature("readAllLocalVariables()") + num_local_vars = len(local_function.local_variables) + check_num_local_vars_read(local_function, op_test.slithir_op, num_local_vars) + + +if __name__ == "__main__": + test_operation_reads() diff --git a/tests/slithir/test_ternary_expressions.py b/tests/slithir/test_ternary_expressions.py index 17cac6b2fc..a1f00eb4fd 100644 --- a/tests/slithir/test_ternary_expressions.py +++ b/tests/slithir/test_ternary_expressions.py @@ -1,3 +1,4 @@ +from solc_select import solc_select from slither import Slither from slither.core.cfg.node import NodeType from slither.slithir.operations import Assignment @@ -6,6 +7,7 @@ # pylint: disable=too-many-nested-blocks def test_ternary_conversions() -> None: """This tests that true and false sons define the same number of variables that the father node declares""" + solc_select.switch_global_version("0.8.0", always_install=True) slither = Slither("./tests/slithir/ternary_expressions.sol") for contract in slither.contracts: for function in contract.functions: diff --git a/tests/src_mapping/ReferencesUserDefinedAliases.sol b/tests/src_mapping/ReferencesUserDefinedAliases.sol new file mode 100644 index 0000000000..68fac48af2 --- /dev/null +++ b/tests/src_mapping/ReferencesUserDefinedAliases.sol @@ -0,0 +1,19 @@ +pragma solidity 0.8.16; + +type aliasTopLevel is uint; + +contract C +{ + type aliasContractLevel is uint; +} + +contract Test +{ + aliasTopLevel a; + C.aliasContractLevel b; +} + +function f(aliasTopLevel, C.aliasContractLevel) +{ + +} \ No newline at end of file diff --git a/tests/src_mapping/ReferencesUserDefinedTypesCasting.sol b/tests/src_mapping/ReferencesUserDefinedTypesCasting.sol new file mode 100644 index 0000000000..1c23f0d5ca --- /dev/null +++ b/tests/src_mapping/ReferencesUserDefinedTypesCasting.sol @@ -0,0 +1,19 @@ +pragma solidity 0.8.16; + +interface A +{ + function a() external; +} + +contract C +{ + function g(address _address) private + { + A(_address).a(); + } +} + +function f(address _address) +{ + A(_address).a(); +} \ No newline at end of file diff --git a/tests/test_ast_parsing.py b/tests/test_ast_parsing.py index 14130b4285..c783f827f9 100644 --- a/tests/test_ast_parsing.py +++ b/tests/test_ast_parsing.py @@ -443,6 +443,7 @@ def make_version(minor: int, patch_min: int, patch_max: int) -> List[str]: Test("top-level-struct-0.8.0.sol", ["0.8.0"]), Test("yul-top-level-0.8.0.sol", ["0.8.0"]), Test("complex_imports/import_aliases_issue_1319/test.sol", ["0.5.12"]), + Test("yul-state-constant-access.sol", ["0.8.16"]), ] # create the output folder if needed try: diff --git a/tests/test_detectors.py b/tests/test_detectors.py index 994f87aac4..9d82afd2cf 100644 --- a/tests/test_detectors.py +++ b/tests/test_detectors.py @@ -371,6 +371,11 @@ def id_test(test_item: Test): "uninitialized_storage_pointer.sol", "0.4.25", ), + Test( + all_detectors.UninitializedStorageVars, + "uninitialized_storage_pointer.sol", + "0.8.19", + ), Test(all_detectors.TxOrigin, "tx_origin.sol", "0.4.25"), Test(all_detectors.TxOrigin, "tx_origin.sol", "0.5.16"), Test(all_detectors.TxOrigin, "tx_origin.sol", "0.6.11"), @@ -609,6 +614,26 @@ def id_test(test_item: Test): "naming_convention.sol", "0.7.6", ), + Test( + all_detectors.NamingConvention, + "no_warning_for_public_constants.sol", + "0.4.25", + ), + Test( + all_detectors.NamingConvention, + "no_warning_for_public_constants.sol", + "0.5.16", + ), + Test( + all_detectors.NamingConvention, + "no_warning_for_public_constants.sol", + "0.6.11", + ), + Test( + all_detectors.NamingConvention, + "no_warning_for_public_constants.sol", + "0.7.6", + ), Test( all_detectors.ControlledDelegateCall, "controlled_delegatecall.sol", @@ -1603,6 +1628,16 @@ def id_test(test_item: Test): "var_read_using_this.sol", "0.8.15", ), + Test( + all_detectors.CyclomaticComplexity, + "HighCyclomaticComplexity.sol", + "0.8.16", + ), + Test( + all_detectors.CyclomaticComplexity, + "LowCyclomaticComplexity.sol", + "0.8.16", + ), ] diff --git a/tests/test_features.py b/tests/test_features.py index 0303a5760b..20393df383 100644 --- a/tests/test_features.py +++ b/tests/test_features.py @@ -1,4 +1,5 @@ import inspect +from pathlib import Path from crytic_compile import CryticCompile from crytic_compile.platform.solc_standard_json import SolcStandardJson @@ -8,7 +9,8 @@ from slither.core.variables.state_variable import StateVariable from slither.detectors import all_detectors from slither.detectors.abstract_detector import AbstractDetector -from slither.slithir.operations import LibraryCall, InternalCall +from slither.slithir.operations import InternalCall, LibraryCall +from slither.utils.arithmetic import unchecked_arithemtic_usage def _run_all_detectors(slither: Slither) -> None: @@ -30,7 +32,7 @@ def test_node() -> None: def test_collision() -> None: - + solc_select.switch_global_version("0.8.0", always_install=True) standard_json = SolcStandardJson() standard_json.add_source_file("./tests/collisions/a.sol") standard_json.add_source_file("./tests/collisions/b.sol") @@ -42,6 +44,7 @@ def test_collision() -> None: def test_cycle() -> None: + solc_select.switch_global_version("0.8.0", always_install=True) slither = Slither("./tests/test_cyclic_import/a.sol") _run_all_detectors(slither) @@ -73,6 +76,36 @@ def test_upgradeable_comments() -> None: assert v1.upgradeable_version == "version_1" +def test_contract_comments() -> None: + comments = " @title Test Contract\n @dev Test comment" + + solc_select.switch_global_version("0.8.10", always_install=True) + slither = Slither("./tests/custom_comments/contract_comment.sol") + compilation_unit = slither.compilation_units[0] + contract = compilation_unit.get_contract_from_name("A")[0] + + assert contract.comments == comments + + # Old solc versions have a different parsing of comments + # the initial space (after *) is also not kept on every line + comments = "@title Test Contract\n@dev Test comment" + solc_select.switch_global_version("0.5.16", always_install=True) + slither = Slither("./tests/custom_comments/contract_comment.sol") + compilation_unit = slither.compilation_units[0] + contract = compilation_unit.get_contract_from_name("A")[0] + + assert contract.comments == comments + + # Test with legacy AST + comments = "@title Test Contract\n@dev Test comment" + solc_select.switch_global_version("0.5.16", always_install=True) + slither = Slither("./tests/custom_comments/contract_comment.sol", solc_force_legacy_json=True) + compilation_unit = slither.compilation_units[0] + contract = compilation_unit.get_contract_from_name("A")[0] + + assert contract.comments == comments + + def test_using_for_top_level_same_name() -> None: solc_select.switch_global_version("0.8.15", always_install=True) slither = Slither("./tests/ast-parsing/using-for-3-0.8.0.sol") @@ -150,3 +183,37 @@ def test_private_variable() -> None: var_read = f.variables_read[0] assert isinstance(var_read, StateVariable) assert str(var_read.contract) == "B" + + +def test_arithmetic_usage() -> None: + solc_select.switch_global_version("0.8.15", always_install=True) + slither = Slither("./tests/arithmetic_usage/test.sol") + + assert { + f.source_mapping.content_hash for f in unchecked_arithemtic_usage(slither.contracts[0]) + } == {"2b4bc73cf59d486dd9043e840b5028b679354dd9", "e4ecd4d0fda7e762d29aceb8425f2c5d4d0bf962"} + + +def test_using_for_global_collision() -> None: + solc_select.switch_global_version("0.8.18", always_install=True) + standard_json = SolcStandardJson() + for source_file in Path("./tests/using-for-global-collision").rglob("*.sol"): + standard_json.add_source_file(Path(source_file).as_posix()) + compilation = CryticCompile(standard_json) + sl = Slither(compilation) + _run_all_detectors(sl) + + +def test_abstract_contract() -> None: + solc_select.switch_global_version("0.8.0", always_install=True) + slither = Slither("./tests/function_features/abstract.sol") + assert not slither.contracts[0].is_fully_implemented + + solc_select.switch_global_version("0.5.0", always_install=True) + slither = Slither("./tests/function_features/implicit_abstract.sol") + assert not slither.contracts[0].is_fully_implemented + + slither = Slither( + "./tests/function_features/implicit_abstract.sol", solc_force_legacy_json=True + ) + assert not slither.contracts[0].is_fully_implemented diff --git a/tests/test_read_storage.py b/tests/test_read_storage.py index 67a89dae87..7aec6ff407 100644 --- a/tests/test_read_storage.py +++ b/tests/test_read_storage.py @@ -1,7 +1,6 @@ -import re -import os -import sys import json +import os +import re import shutil import subprocess from time import sleep @@ -9,17 +8,12 @@ import pytest from deepdiff import DeepDiff +from web3 import Web3 +from web3.contract import Contract + from slither import Slither from slither.tools.read_storage import SlitherReadStorage -try: - from web3 import Web3 - from web3.contract import Contract -except ImportError: - print("ERROR: in order to use slither-read-storage, you need to install web3") - print("$ pip3 install web3 --user\n") - sys.exit(-1) - SLITHER_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) STORAGE_TEST_ROOT = os.path.join(SLITHER_ROOT, "tests", "storage-layout") @@ -98,7 +92,7 @@ def deploy_contract(w3, ganache, contract_bin, contract_abi) -> Contract: # pylint: disable=too-many-locals @pytest.mark.usefixtures("web3", "ganache") def test_read_storage(web3, ganache) -> None: - assert web3.isConnected() + assert web3.is_connected() bin_path = os.path.join(STORAGE_TEST_ROOT, "StorageLayout.bin") abi_path = os.path.join(STORAGE_TEST_ROOT, "StorageLayout.abi") bytecode = get_source_file(bin_path) diff --git a/tests/test_source_mapping.py b/tests/test_source_mapping.py index 2ada121fe3..c5ff28c67d 100644 --- a/tests/test_source_mapping.py +++ b/tests/test_source_mapping.py @@ -81,3 +81,53 @@ def test_source_mapping(): (x.start, x.end) for x in slither.offset_to_implementations("tests/src_mapping/inheritance.sol", 93) } == {(17, 53), (193, 230), (129, 166)} + + +def _sort_references_lines(refs: list) -> list: + return sorted([ref.lines[0] for ref in refs]) + + +def _test_references_user_defined_aliases(): + """ + Tests if references are filled correctly for user defined aliases (declared using "type [...] is [...]" statement). + """ + solc_select.switch_global_version("0.8.16", always_install=True) + slither = Slither("tests/src_mapping/ReferencesUserDefinedAliases.sol") + + alias_top_level = slither.compilation_units[0].user_defined_value_types["aliasTopLevel"] + assert len(alias_top_level.references) == 2 + lines = _sort_references_lines(alias_top_level.references) + assert lines == [12, 16] + + alias_contract_level = ( + slither.compilation_units[0] + .contracts[0] + .file_scope.user_defined_types["C.aliasContractLevel"] + ) + assert len(alias_contract_level.references) == 2 + lines = _sort_references_lines(alias_contract_level.references) + assert lines == [13, 16] + + +def _test_references_user_defined_types_when_casting(): + """ + Tests if references are filled correctly for user defined types in case of casting. + """ + solc_select.switch_global_version("0.8.16", always_install=True) + slither = Slither("tests/src_mapping/ReferencesUserDefinedTypesCasting.sol") + + contracts = slither.compilation_units[0].contracts + a = contracts[0] if contracts[0].is_interface else contracts[1] + assert len(a.references) == 2 + lines = _sort_references_lines(a.references) + assert lines == [12, 18] + + +def test_references(): + """ + Tests if references list is filled correctly in the following cases: + - user defined aliases (declared using "type [...] is [...]" statement) + - user defined types in case of casting (TypeConversion expressions) + """ + _test_references_user_defined_aliases() + _test_references_user_defined_types_when_casting() diff --git a/tests/test_ssa_generation.py b/tests/test_ssa_generation.py index f002ec4e13..00d2f23fd4 100644 --- a/tests/test_ssa_generation.py +++ b/tests/test_ssa_generation.py @@ -27,6 +27,7 @@ BinaryType, InternalCall, Index, + InitArray, ) from slither.slithir.utils.ssa import is_used_later from slither.slithir.variables import ( @@ -796,20 +797,20 @@ def test_memory_array(): uint val1; uint val2; } - + function test_array() internal { A[] memory a= new A[](4); // Create REF_0 -> a_1[2] accept_array_entry(a[2]); - + // Create REF_1 -> a_1[3] accept_array_entry(a[3]); - + A memory alocal; accept_array_entry(alocal); - + } - + // val_1 = ϕ(val_0, REF_0, REF_1, alocal_1) // val_0 is an unknown external value function accept_array_entry(A memory val) public returns (uint) { @@ -818,9 +819,9 @@ def test_memory_array(): // Create REF_2 -> val_1.val1 return b(val.val1); } - + function b(uint arg) public returns (uint){ - // arg_1 = ϕ(arg_0, zero_1, REF_2) + // arg_1 = ϕ(arg_0, zero_1, REF_2) return arg + 1; } }""" @@ -862,12 +863,12 @@ def test_storage_array(): uint val1; uint val2; } - + // NOTE(hbrodin): a is never written, should only become a_0. Same for astorage (astorage_0). Phi-nodes at entry - // should only add new versions of a state variable if it is actually written. + // should only add new versions of a state variable if it is actually written. A[] a; A astorage; - + function test_array() internal { accept_array_entry(a[2]); accept_array_entry(a[3]); @@ -877,7 +878,7 @@ def test_storage_array(): function accept_array_entry(A storage val) internal returns (uint) { // val is either a[2], a[3] or astorage_0. Ideally this could be identified. uint five = 5; - + // NOTE(hbrodin): If the following line is enabled, there would ideally be a phi-node representing writes // to either a or astorage. //val.val2 = 4; @@ -1059,3 +1060,20 @@ def test_issue_473(): # return is for second phi assert len(return_value.values) == 1 assert second_phi.lvalue in return_value.values + + +def test_issue_1748(): + source = """ + contract Contract { + uint[] arr; + function foo(uint i) public { + arr = [1]; + } + } + """ + with slither_from_source(source) as slither: + c = slither.get_contract_from_name("Contract")[0] + f = c.functions[0] + operations = f.slithir_operations + assign_op = operations[0] + assert isinstance(assign_op, InitArray) diff --git a/tests/using-for-global-collision/src/MyTypeA.sol b/tests/using-for-global-collision/src/MyTypeA.sol new file mode 100644 index 0000000000..dbb00faf20 --- /dev/null +++ b/tests/using-for-global-collision/src/MyTypeA.sol @@ -0,0 +1,2 @@ +import "./MyTypeA/Type.sol"; +import "./MyTypeA/Math.sol"; \ No newline at end of file diff --git a/tests/using-for-global-collision/src/MyTypeA/Casting.sol b/tests/using-for-global-collision/src/MyTypeA/Casting.sol new file mode 100644 index 0000000000..c166436fe2 --- /dev/null +++ b/tests/using-for-global-collision/src/MyTypeA/Casting.sol @@ -0,0 +1,4 @@ +import "./Type.sol"; +function unwrap(MyTypeA a) pure returns (int256) { + return MyTypeA.unwrap(a); +} \ No newline at end of file diff --git a/tests/using-for-global-collision/src/MyTypeA/Math.sol b/tests/using-for-global-collision/src/MyTypeA/Math.sol new file mode 100644 index 0000000000..21f7c79251 --- /dev/null +++ b/tests/using-for-global-collision/src/MyTypeA/Math.sol @@ -0,0 +1,5 @@ +import "./Type.sol"; + +function mul(MyTypeA a, MyTypeA b) pure returns (MyTypeA) { + return MyTypeA.wrap(MyTypeA.unwrap(a) * MyTypeA.unwrap(b)); +} diff --git a/tests/using-for-global-collision/src/MyTypeA/Type.sol b/tests/using-for-global-collision/src/MyTypeA/Type.sol new file mode 100644 index 0000000000..0973c78692 --- /dev/null +++ b/tests/using-for-global-collision/src/MyTypeA/Type.sol @@ -0,0 +1,6 @@ +import "./Casting.sol" as C; +import "./Math.sol" as M; + +type MyTypeA is int256; + +using {M.mul, C.unwrap} for MyTypeA global; \ No newline at end of file diff --git a/tests/using-for-global-collision/src/MyTypeB.sol b/tests/using-for-global-collision/src/MyTypeB.sol new file mode 100644 index 0000000000..3ddde2ac65 --- /dev/null +++ b/tests/using-for-global-collision/src/MyTypeB.sol @@ -0,0 +1,2 @@ +import "./MyTypeB/Type.sol"; +import "./MyTypeB/Math.sol"; \ No newline at end of file diff --git a/tests/using-for-global-collision/src/MyTypeB/Casting.sol b/tests/using-for-global-collision/src/MyTypeB/Casting.sol new file mode 100644 index 0000000000..c400a91123 --- /dev/null +++ b/tests/using-for-global-collision/src/MyTypeB/Casting.sol @@ -0,0 +1,4 @@ +import "./Type.sol"; +function unwrap(MyTypeB a) pure returns (uint256) { + return MyTypeB.unwrap(a); +} \ No newline at end of file diff --git a/tests/using-for-global-collision/src/MyTypeB/Math.sol b/tests/using-for-global-collision/src/MyTypeB/Math.sol new file mode 100644 index 0000000000..24ee1a5826 --- /dev/null +++ b/tests/using-for-global-collision/src/MyTypeB/Math.sol @@ -0,0 +1,6 @@ +import "./Type.sol"; + +function mul(MyTypeB a, MyTypeB b) pure returns (MyTypeB) { + return MyTypeB.wrap(MyTypeB.unwrap(a) * MyTypeB.unwrap(b)); +} + diff --git a/tests/using-for-global-collision/src/MyTypeB/Type.sol b/tests/using-for-global-collision/src/MyTypeB/Type.sol new file mode 100644 index 0000000000..a66b65f5dc --- /dev/null +++ b/tests/using-for-global-collision/src/MyTypeB/Type.sol @@ -0,0 +1,6 @@ +import "./Casting.sol" as C; +import "./Math.sol" as M; + +type MyTypeB is uint256; + +using {M.mul, C.unwrap} for MyTypeB global; \ No newline at end of file diff --git a/tests/using-for-global-collision/src/Test.sol b/tests/using-for-global-collision/src/Test.sol new file mode 100644 index 0000000000..013570048c --- /dev/null +++ b/tests/using-for-global-collision/src/Test.sol @@ -0,0 +1,7 @@ +import "./MyTypeB.sol"; + +contract UsingForGlobalTopLevelCollision { + function mulAndUnwrap(MyTypeB x, MyTypeB y) external pure returns (uint256 z) { + z = x.mul(y).unwrap(); + } +} \ No newline at end of file