Merge branch 'dev' into halstead

pull/1878/head
devtooligan 1 year ago
commit c175d527ff
No known key found for this signature in database
GPG Key ID: A5606B287899E7FB
  1. 8
      .github/dependabot.yml
  2. 2
      .github/workflows/black.yml
  3. 11
      .github/workflows/ci.yml
  4. 2
      .github/workflows/docker.yml
  5. 2
      .github/workflows/docs.yml
  6. 2
      .github/workflows/linter.yml
  7. 54
      .github/workflows/publish.yml
  8. 2
      .github/workflows/pylint.yml
  9. 7
      .github/workflows/test.yml
  10. 7
      CONTRIBUTING.md
  11. 212
      README.md
  12. 2
      scripts/ci_test_printers.sh
  13. 77
      slither/__main__.py
  14. 6
      slither/core/cfg/scope.py
  15. 2
      slither/core/declarations/event.py
  16. 5
      slither/core/solidity_types/type_alias.py
  17. 1
      slither/detectors/all_detectors.py
  18. 13
      slither/detectors/compiler_bugs/array_by_reference.py
  19. 221
      slither/detectors/operations/cache_array_length.py
  20. 2
      slither/detectors/statements/msg_value_in_loop.py
  21. 18
      slither/detectors/variables/similar_variables.py
  22. 1
      slither/printers/all_printers.py
  23. 17
      slither/printers/guidance/echidna.py
  24. 85
      slither/printers/summary/human_summary.py
  25. 35
      slither/printers/summary/loc.py
  26. 7
      slither/slithir/operations/index.py
  27. 3
      slither/slithir/variables/reference.py
  28. 147
      slither/solc_parsing/declarations/function.py
  29. 48
      slither/solc_parsing/declarations/using_for_top_level.py
  30. 60
      slither/solc_parsing/expressions/expression_parsing.py
  31. 18
      slither/solc_parsing/variables/local_variable_init_from_tuple.py
  32. 21
      slither/tools/interface/README.md
  33. 9
      slither/tools/interface/__main__.py
  34. 33
      slither/tools/read_storage/README.md
  35. 7
      slither/tools/read_storage/__main__.py
  36. 315
      slither/tools/read_storage/read_storage.py
  37. 2
      slither/tools/read_storage/utils/utils.py
  38. 50
      slither/utils/command_line.py
  39. 4
      slither/utils/integer_conversion.py
  40. 105
      slither/utils/loc.py
  41. 222
      slither/utils/upgradeability.py
  42. 71
      slither/visitors/expression/constants_folding.py
  43. 6
      tests/conftest.py
  44. 14
      tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_4_25_array_by_reference_sol__0.txt
  45. 14
      tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_5_16_array_by_reference_sol__0.txt
  46. 14
      tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_6_11_array_by_reference_sol__0.txt
  47. 14
      tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_7_6_array_by_reference_sol__0.txt
  48. 4
      tests/e2e/detectors/snapshots/detectors__detector_BuiltinSymbolShadowing_0_4_25_shadowing_builtin_symbols_sol__0.txt
  49. 4
      tests/e2e/detectors/snapshots/detectors__detector_BuiltinSymbolShadowing_0_5_16_shadowing_builtin_symbols_sol__0.txt
  50. 18
      tests/e2e/detectors/snapshots/detectors__detector_CacheArrayLength_0_8_17_CacheArrayLength_sol__0.txt
  51. 2
      tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_4_25_shadowing_local_variable_sol__0.txt
  52. 2
      tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_5_16_shadowing_local_variable_sol__0.txt
  53. 2
      tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_6_11_shadowing_local_variable_sol__0.txt
  54. 2
      tests/e2e/detectors/snapshots/detectors__detector_LocalShadowing_0_7_6_shadowing_local_variable_sol__0.txt
  55. 4
      tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_4_25_naming_convention_sol__0.txt
  56. 4
      tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_5_16_naming_convention_sol__0.txt
  57. 4
      tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_6_11_naming_convention_sol__0.txt
  58. 4
      tests/e2e/detectors/snapshots/detectors__detector_NamingConvention_0_7_6_naming_convention_sol__0.txt
  59. 8
      tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_4_25_erc20_indexed_sol__0.txt
  60. 8
      tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_5_16_erc20_indexed_sol__0.txt
  61. 8
      tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_6_11_erc20_indexed_sol__0.txt
  62. 8
      tests/e2e/detectors/snapshots/detectors__detector_UnindexedERC20EventParameters_0_7_6_erc20_indexed_sol__0.txt
  63. 21
      tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol
  64. BIN
      tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol-0.4.25.zip
  65. 21
      tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol
  66. BIN
      tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol-0.5.16.zip
  67. 21
      tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol
  68. BIN
      tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol-0.6.11.zip
  69. 21
      tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol
  70. BIN
      tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol-0.7.6.zip
  71. 171
      tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol
  72. BIN
      tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol-0.8.17.zip
  73. 11
      tests/e2e/detectors/test_detectors.py
  74. 1
      tests/e2e/solc_parsing/test_ast_parsing.py
  75. BIN
      tests/e2e/solc_parsing/test_data/compile/user_defined_operators-0.8.19.sol-0.8.19-compact.zip
  76. 13
      tests/e2e/solc_parsing/test_data/expected/user_defined_operators-0.8.19.sol-0.8.19-compact.json
  77. 48
      tests/e2e/solc_parsing/test_data/user_defined_operators-0.8.19.sol
  78. 0
      tests/tools/check_erc/erc20.sol
  79. 0
      tests/tools/check_erc/test_1.txt
  80. 0
      tests/tools/check_kspec/safeAdd/safeAdd.sol
  81. 0
      tests/tools/check_kspec/safeAdd/spec.md
  82. 0
      tests/tools/check_kspec/test_1.txt
  83. 0
      tests/tools/check_upgradeability/contractV1.sol
  84. 0
      tests/tools/check_upgradeability/contractV1_struct.sol
  85. 0
      tests/tools/check_upgradeability/contractV2.sol
  86. 0
      tests/tools/check_upgradeability/contractV2_bug.sol
  87. 0
      tests/tools/check_upgradeability/contractV2_bug2.sol
  88. 0
      tests/tools/check_upgradeability/contractV2_struct.sol
  89. 0
      tests/tools/check_upgradeability/contractV2_struct_bug.sol
  90. 0
      tests/tools/check_upgradeability/contract_initialization.sol
  91. 0
      tests/tools/check_upgradeability/contract_v1_var_init.sol
  92. 0
      tests/tools/check_upgradeability/contract_v2_constant.sol
  93. 0
      tests/tools/check_upgradeability/proxy.sol
  94. 0
      tests/tools/check_upgradeability/test_1.txt
  95. 0
      tests/tools/check_upgradeability/test_10.txt
  96. 0
      tests/tools/check_upgradeability/test_11.txt
  97. 0
      tests/tools/check_upgradeability/test_12.txt
  98. 0
      tests/tools/check_upgradeability/test_13.txt
  99. 0
      tests/tools/check_upgradeability/test_2.txt
  100. 0
      tests/tools/check_upgradeability/test_3.txt
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,8 @@
---
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "dev"
schedule:
interval: "weekly"

@ -32,7 +32,7 @@ jobs:
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v3
uses: actions/setup-python@v4
with:
python-version: 3.8

@ -26,6 +26,7 @@ jobs:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-2022"]
python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.11"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11"]') }}
type: ["cli",
"dapp",
"data_dependency",
@ -53,10 +54,10 @@ jobs:
type: truffle
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v3
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: 3.8
python-version: ${{ matrix.python }}
- name: Install dependencies
run: |
pip install ".[test]"
@ -66,11 +67,11 @@ jobs:
- name: Set up nix
if: matrix.type == 'dapp'
uses: cachix/install-nix-action@v20
uses: cachix/install-nix-action@v22
- name: Set up cachix
if: matrix.type == 'dapp'
uses: cachix/cachix-action@v10
uses: cachix/cachix-action@v12
with:
name: dapp

@ -47,7 +47,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker Build and Push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
with:
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7
target: final

@ -43,4 +43,4 @@ jobs:
path: './html/'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1
uses: actions/deploy-pages@v2

@ -33,7 +33,7 @@ jobs:
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v3
uses: actions/setup-python@v4
with:
python-version: 3.8

@ -0,0 +1,54 @@
name: Publish to PyPI
on:
release:
types: [published]
jobs:
build-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Build distributions
run: |
python -m pip install --upgrade pip
python -m pip install build
python -m build
- name: Upload distributions
uses: actions/upload-artifact@v3
with:
name: slither-dists
path: dist/
publish:
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write # For trusted publishing + codesigning.
contents: write # For attaching signing artifacts to the release.
needs:
- build-release
steps:
- name: fetch dists
uses: actions/download-artifact@v3
with:
name: slither-dists
path: dist/
- name: publish
uses: pypa/gh-action-pypi-publish@v1.8.7
- name: sign
uses: sigstore/gh-action-sigstore-python@v1.2.3
with:
inputs: ./dist/*.tar.gz ./dist/*.whl
release-signing-artifacts: true
bundle-only: true

@ -27,7 +27,7 @@ jobs:
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v3
uses: actions/setup-python@v4
with:
python-version: 3.8

@ -25,12 +25,13 @@ jobs:
matrix:
os: ["ubuntu-latest", "windows-2022"]
type: ["unit", "integration", "tool"]
python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.11"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11"]') }}
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.8
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: 3.8
python-version: ${{ matrix.python }}
cache: "pip"
cache-dependency-path: setup.py
@ -74,7 +75,7 @@ jobs:
uses: ./.github/actions/upload-coverage
# only aggregate test coverage over linux-based tests to avoid any OS-specific filesystem information stored in
# coverage metadata.
if: ${{ matrix.os == 'ubuntu-latest' }}
if: ${{ matrix.os == 'ubuntu-latest' && matrix.python == '3.8' }}
coverage:
needs:

@ -81,7 +81,7 @@ For each new detector, at least one regression tests must be present.
1. Create a folder in `tests/e2e/detectors/test_data` with the detector's argument name.
2. Create a test contract in `tests/e2e/detectors/test_data/<detector_name>/`.
3. Update `ALL_TEST` in `tests/e2e/detectors/test_detectors.py`
3. Update `ALL_TESTS` in `tests/e2e/detectors/test_detectors.py`.
4. Run `python tests/e2e/detectors/test_detectors.py --compile` to create a zip file of the compilation artifacts.
5. `pytest tests/e2e/detectors/test_detectors.py --insta update-new`. This will generate a snapshot of the detector output in `tests/e2e/detectors/snapshots/`. If updating an existing detector, run `pytest tests/e2e/detectors/test_detectors.py --insta review` and accept or reject the updates.
6. Run `pytest tests/e2e/detectors/test_detectors.py` to ensure everything worked. Then, add and commit the files to git.
@ -97,8 +97,9 @@ For each new detector, at least one regression tests must be present.
1. Create a test in `tests/e2e/solc_parsing/`
2. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --compile`. This will compile the artifact in `tests/e2e/solc_parsing/compile`. Add the compiled artifact to git.
3. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --generate`. This will generate the json artifacts in `tests/e2e/solc_parsing/expected_json`. Add the generated files to git.
4. Run `pytest tests/e2e/solc_parsing/test_ast_parsing.py` and check that everything worked.
3. Update `ALL_TESTS` in `tests/e2e/solc_parsing/test_ast_parsing.py`.
4. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --generate`. This will generate the json artifacts in `tests/e2e/solc_parsing/expected_json`. Add the generated files to git.
5. Run `pytest tests/e2e/solc_parsing/test_ast_parsing.py` and check that everything worked.
> ##### Helpful commands for parsing tests
>

@ -1,5 +1,6 @@
# Slither, the Solidity source analyzer
<img src="./logo.png" alt="Logo" width="500"/>
<img src="https://raw.githubusercontent.com/crytic/slither/master/logo.png" alt="Logo" width="500"/>
[![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)
@ -20,26 +21,29 @@ Slither is a Solidity static analysis framework written in Python3. It runs a su
## Features
* 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 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)
- 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 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
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.
However, you can run Slither on a single file that does not import dependencies:
```bash
slither tests/uninitialized.sol
```
@ -79,118 +83,122 @@ 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`)
## Detectors
Num | Detector | What it Detects | Impact | Confidence
--- | --- | --- | --- | ---
1 | `abiencoderv2-array` | [Storage abiencoderv2 array](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-abiencoderv2-array) | High | High
2 | `arbitrary-send-erc20` | [transferFrom uses arbitrary `from`](https://github.com/crytic/slither/wiki/Detector-Documentation#arbitrary-from-in-transferfrom) | High | High
3 | `array-by-reference` | [Modifying storage array by value](https://github.com/crytic/slither/wiki/Detector-Documentation#modifying-storage-array-by-value) | High | High
4 | `incorrect-shift` | [The order of parameters in a shift instruction is incorrect.](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-shift-in-assembly) | High | High
5 | `multiple-constructors` | [Multiple constructor schemes](https://github.com/crytic/slither/wiki/Detector-Documentation#multiple-constructor-schemes) | High | High
6 | `name-reused` | [Contract's name reused](https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused) | High | High
7 | `protected-vars` | [Detected unprotected variables](https://github.com/crytic/slither/wiki/Detector-Documentation#protected-variables) | High | High
8 | `public-mappings-nested` | [Public mappings with nested variables](https://github.com/crytic/slither/wiki/Detector-Documentation#public-mappings-with-nested-variables) | High | High
9 | `rtlo` | [Right-To-Left-Override control character is used](https://github.com/crytic/slither/wiki/Detector-Documentation#right-to-left-override-character) | High | High
10 | `shadowing-state` | [State variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing) | High | High
11 | `suicidal` | [Functions allowing anyone to destruct the contract](https://github.com/crytic/slither/wiki/Detector-Documentation#suicidal) | High | High
12 | `uninitialized-state` | [Uninitialized state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables) | High | High
13 | `uninitialized-storage` | [Uninitialized storage variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables) | High | High
14 | `unprotected-upgrade` | [Unprotected upgradeable contract](https://github.com/crytic/slither/wiki/Detector-Documentation#unprotected-upgradeable-contract) | High | High
15 | `codex` | [Use Codex to find vulnerabilities.](https://github.com/crytic/slither/wiki/Detector-Documentation#codex) | High | Low
16 | `arbitrary-send-erc20-permit` | [transferFrom uses arbitrary from with permit](https://github.com/crytic/slither/wiki/Detector-Documentation#arbitrary-from-in-transferfrom-used-with-permit) | High | Medium
17 | `arbitrary-send-eth` | [Functions that send Ether to arbitrary destinations](https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations) | High | Medium
18 | `controlled-array-length` | [Tainted array length assignment](https://github.com/crytic/slither/wiki/Detector-Documentation#array-length-assignment) | High | Medium
19 | `controlled-delegatecall` | [Controlled delegatecall destination](https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall) | High | Medium
20 | `delegatecall-loop` | [Payable functions using `delegatecall` inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#payable-functions-using-delegatecall-inside-a-loop) | High | Medium
21 | `msg-value-loop` | [msg.value inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#msgvalue-inside-a-loop) | High | Medium
22 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | High | Medium
23 | `storage-array` | [Signed storage integer array compiler bug](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-signed-integer-array) | High | Medium
24 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium
25 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | High | Medium
26 | `domain-separator-collision` | [Detects ERC20 tokens that have a function whose signature collides with EIP-2612's DOMAIN_SEPARATOR()](https://github.com/crytic/slither/wiki/Detector-Documentation#domain-separator-collision) | Medium | High
27 | `enum-conversion` | [Detect dangerous enum conversion](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-enum-conversion) | Medium | High
28 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface) | Medium | High
29 | `erc721-interface` | [Incorrect ERC721 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface) | Medium | High
30 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities) | Medium | High
31 | `locked-ether` | [Contracts that lock ether](https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether) | Medium | High
32 | `mapping-deletion` | [Deletion on mapping containing a structure](https://github.com/crytic/slither/wiki/Detector-Documentation#deletion-on-mapping-containing-a-structure) | Medium | High
33 | `shadowing-abstract` | [State variables shadowing from abstract contracts](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts) | Medium | High
34 | `tautology` | [Tautology or contradiction](https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction) | Medium | High
35 | `write-after-write` | [Unused write](https://github.com/crytic/slither/wiki/Detector-Documentation#write-after-write) | Medium | High
36 | `boolean-cst` | [Misuse of Boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant) | Medium | Medium
37 | `constant-function-asm` | [Constant functions using assembly code](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code) | Medium | Medium
38 | `constant-function-state` | [Constant functions changing the state](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state) | Medium | Medium
39 | `divide-before-multiply` | [Imprecise arithmetic operations order](https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply) | Medium | Medium
40 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1) | Medium | Medium
41 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | Medium | Medium
42 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin) | Medium | Medium
43 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium
44 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | Medium | Medium
45 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium
46 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
47 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | Low | High
48 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High
49 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
50 | `uninitialized-fptr-cst` | [Uninitialized function pointer calls in constructors](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-function-pointers-in-constructors) | Low | High
51 | `variable-scope` | [Local variables used prior their declaration](https://github.com/crytic/slither/wiki/Detector-Documentation#pre-declaration-usage-of-local-variables) | Low | High
52 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High
53 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium
54 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium
55 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium
56 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | Low | Medium
57 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium
58 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
59 | `reentrancy-events` | [Reentrancy vulnerabilities leading to out-of-order Events](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3) | Low | Medium
60 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
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 | `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
4 | `encode-packed-collision` | [ABI encodePacked Collision](https://github.com/crytic/slither/wiki/Detector-Documentation#abi-encodePacked-collision) | High | High
5 | `incorrect-shift` | [The order of parameters in a shift instruction is incorrect.](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-shift-in-assembly) | High | High
6 | `multiple-constructors` | [Multiple constructor schemes](https://github.com/crytic/slither/wiki/Detector-Documentation#multiple-constructor-schemes) | High | High
7 | `name-reused` | [Contract's name reused](https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused) | High | High
8 | `protected-vars` | [Detected unprotected variables](https://github.com/crytic/slither/wiki/Detector-Documentation#protected-variables) | High | High
9 | `public-mappings-nested` | [Public mappings with nested variables](https://github.com/crytic/slither/wiki/Detector-Documentation#public-mappings-with-nested-variables) | High | High
10 | `rtlo` | [Right-To-Left-Override control character is used](https://github.com/crytic/slither/wiki/Detector-Documentation#right-to-left-override-character) | High | High
11 | `shadowing-state` | [State variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing) | High | High
12 | `suicidal` | [Functions allowing anyone to destruct the contract](https://github.com/crytic/slither/wiki/Detector-Documentation#suicidal) | High | High
13 | `uninitialized-state` | [Uninitialized state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables) | High | High
14 | `uninitialized-storage` | [Uninitialized storage variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables) | High | High
15 | `unprotected-upgrade` | [Unprotected upgradeable contract](https://github.com/crytic/slither/wiki/Detector-Documentation#unprotected-upgradeable-contract) | High | High
16 | `codex` | [Use Codex to find vulnerabilities.](https://github.com/crytic/slither/wiki/Detector-Documentation#codex) | High | Low
17 | `arbitrary-send-erc20-permit` | [transferFrom uses arbitrary from with permit](https://github.com/crytic/slither/wiki/Detector-Documentation#arbitrary-from-in-transferfrom-used-with-permit) | High | Medium
18 | `arbitrary-send-eth` | [Functions that send Ether to arbitrary destinations](https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations) | High | Medium
19 | `controlled-array-length` | [Tainted array length assignment](https://github.com/crytic/slither/wiki/Detector-Documentation#array-length-assignment) | High | Medium
20 | `controlled-delegatecall` | [Controlled delegatecall destination](https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall) | High | Medium
21 | `delegatecall-loop` | [Payable functions using `delegatecall` inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#payable-functions-using-delegatecall-inside-a-loop) | High | Medium
22 | `msg-value-loop` | [msg.value inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#msgvalue-inside-a-loop) | High | Medium
23 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | High | Medium
24 | `storage-array` | [Signed storage integer array compiler bug](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-signed-integer-array) | High | Medium
25 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium
26 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | High | Medium
27 | `domain-separator-collision` | [Detects ERC20 tokens that have a function whose signature collides with EIP-2612's DOMAIN_SEPARATOR()](https://github.com/crytic/slither/wiki/Detector-Documentation#domain-separator-collision) | Medium | High
28 | `enum-conversion` | [Detect dangerous enum conversion](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-enum-conversion) | Medium | High
29 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface) | Medium | High
30 | `erc721-interface` | [Incorrect ERC721 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface) | Medium | High
31 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities) | Medium | High
32 | `locked-ether` | [Contracts that lock ether](https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether) | Medium | High
33 | `mapping-deletion` | [Deletion on mapping containing a structure](https://github.com/crytic/slither/wiki/Detector-Documentation#deletion-on-mapping-containing-a-structure) | Medium | High
34 | `shadowing-abstract` | [State variables shadowing from abstract contracts](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts) | Medium | High
35 | `tautology` | [Tautology or contradiction](https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction) | Medium | High
36 | `write-after-write` | [Unused write](https://github.com/crytic/slither/wiki/Detector-Documentation#write-after-write) | Medium | High
37 | `boolean-cst` | [Misuse of Boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant) | Medium | Medium
38 | `constant-function-asm` | [Constant functions using assembly code](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code) | Medium | Medium
39 | `constant-function-state` | [Constant functions changing the state](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state) | Medium | Medium
40 | `divide-before-multiply` | [Imprecise arithmetic operations order](https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply) | Medium | Medium
41 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1) | Medium | Medium
42 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | Medium | Medium
43 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin) | Medium | Medium
44 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium
45 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | Medium | Medium
46 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium
47 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
48 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | Low | High
49 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High
50 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
51 | `uninitialized-fptr-cst` | [Uninitialized function pointer calls in constructors](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-function-pointers-in-constructors) | Low | High
52 | `variable-scope` | [Local variables used prior their declaration](https://github.com/crytic/slither/wiki/Detector-Documentation#pre-declaration-usage-of-local-variables) | Low | High
53 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High
54 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium
55 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium
56 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium
57 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | Low | Medium
58 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium
59 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
60 | `reentrancy-events` | [Reentrancy vulnerabilities leading to out-of-order Events](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3) | Low | Medium
61 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
62 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
63 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High
64 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High
65 | `cyclomatic-complexity` | [Detects functions with high (> 11) cyclomatic complexity](https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity) | Informational | High
66 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
67 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
68 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state) | Informational | High
69 | `incorrect-using-for` | [Detects using-for statement usage when no function from a given library matches a given type](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-using-for-usage) | Informational | High
70 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
71 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | Informational | High
72 | `naming-convention` | [Conformity to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
73 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
74 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
75 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
76 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
77 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High
78 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
79 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
80 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
81 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
82 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
83 | `cache-array-length` | [Detects `for` loops that use `length` member of some storage array in their loop condition and don't modify it.](https://github.com/crytic/slither/wiki/Detector-Documentation#cache-array-length) | Optimization | High
84 | `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
85 | `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
86 | `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
87 | `var-read-using-this` | [Contract reads its own variable using `this`](https://github.com/crytic/slither/wiki/Detector-Documentation#public-variable-read-in-external-context) | Optimization | High
For more information, see
- The [Detector Documentation](https://github.com/crytic/slither/wiki/Detector-Documentation) for details on each detector
- The [Detection Selection](https://github.com/crytic/slither/wiki/Usage#detector-selection) to run only selected detectors. By default, all the detectors are run.
- The [Triage Mode](https://github.com/crytic/slither/wiki/Usage#triage-mode) to filter individual results
## Printers
### Quick Review Printers
- `human-summary`: [Print a human-readable summary of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#human-summary)
- `inheritance-graph`: [Export the inheritance graph of each contract to a dot file](https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance-graph)
- `contract-summary`: [Print a summary of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#contract-summary)
- `loc`: [Count the total number lines of code (LOC), source lines of code (SLOC), and comment lines of code (CLOC) found in source files (SRC), dependencies (DEP), and test files (TEST).](https://github.com/trailofbits/slither/wiki/Printer-documentation#loc)
### In-Depth Review Printers
- `call-graph`: [Export the call-graph of the contracts to a dot file](https://github.com/trailofbits/slither/wiki/Printer-documentation#call-graph)
- `cfg`: [Export the CFG of each functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#cfg)
- `function-summary`: [Print a summary of the functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#function-summary)
- `vars-and-auth`: [Print the state variables written and the authorization of the functions](https://github.com/crytic/slither/wiki/Printer-documentation#variables-written-and-authorization)
- `when-not-paused`: [Print functions that do not use `whenNotPaused` modifier](https://github.com/trailofbits/slither/wiki/Printer-documentation#when-not-paused).
- `not-pausable`: [Print functions that do not use `whenNotPaused` modifier](https://github.com/trailofbits/slither/wiki/Printer-documentation#when-not-paused).
To run a printer, use `--print` and a comma-separated list of printers.
@ -204,32 +212,36 @@ See the [Printer documentation](https://github.com/crytic/slither/wiki/Printer-d
- `slither-check-erc`: [Check the ERC's conformance](https://github.com/crytic/slither/wiki/ERC-Conformance)
- `slither-format`: [Automatic patch generation](https://github.com/crytic/slither/wiki/Slither-format)
- `slither-read-storage`: [Read storage values from contracts](./slither/tools/read_storage/README.md)
- `slither-interface`: [Generate an interface for a contract](./slither/tools/interface/README.md)
See the [Tool documentation](https://github.com/crytic/slither/wiki/Tool-Documentation) for additional tools.
[Contact us](https://www.trailofbits.com/contact/) to get help on building custom tools.
## API Documentation
Documentation on Slither's internals is available [here](https://crytic.github.io/slither/slither.html).
## Getting Help
Feel free to stop by our [Slack channel](https://empireslacking.herokuapp.com) (#ethereum) for help using or extending Slither.
* The [Printer documentation](https://github.com/trailofbits/slither/wiki/Printer-documentation) describes the information Slither is capable of visualizing for each contract.
- The [Printer documentation](https://github.com/trailofbits/slither/wiki/Printer-documentation) describes the information Slither is capable of visualizing for each contract.
* The [Detector documentation](https://github.com/trailofbits/slither/wiki/Adding-a-new-detector) describes how to write a new vulnerability analyses.
- The [Detector documentation](https://github.com/trailofbits/slither/wiki/Adding-a-new-detector) describes how to write a new vulnerability analyses.
* The [API documentation](https://github.com/crytic/slither/wiki/Python-API) describes the methods and objects available for custom analyses.
- The [API documentation](https://github.com/crytic/slither/wiki/Python-API) describes the methods and objects available for custom analyses.
* The [SlithIR documentation](https://github.com/trailofbits/slither/wiki/SlithIR) describes the SlithIR intermediate representation.
- The [SlithIR documentation](https://github.com/trailofbits/slither/wiki/SlithIR) describes the SlithIR intermediate representation.
## FAQ
How do I exclude mocks or tests?
- View our documentation on [path filtering](https://github.com/crytic/slither/wiki/Usage#path-filtering).
How do I fix "unknown file" or compilation issues?
- Because slither requires the solc AST, it must have all dependencies available.
If a contract has dependencies, `slither contract.sol` will fail.
Instead, use `slither .` in the parent directory of `contracts/` (you should see `contracts/` when you run `ls`).
@ -244,9 +256,11 @@ Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailt
## Publications
### Trail of Bits publication
- [Slither: A Static Analysis Framework For Smart Contracts](https://arxiv.org/abs/1908.09878), Josselin Feist, Gustavo Grieco, Alex Groce - WETSEB '19
### External publications
Title | Usage | Authors | Venue | Code
--- | --- | --- | --- | ---
[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

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

@ -35,6 +35,7 @@ from slither.utils.output import (
from slither.utils.output_capture import StandardOutputCapture
from slither.utils.colors import red, set_colorization_enabled
from slither.utils.command_line import (
FailOnLevel,
output_detectors,
output_results_to_markdown,
output_detectors_json,
@ -206,22 +207,22 @@ def choose_detectors(
detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT)
return detectors_to_run
if args.exclude_optimization and not args.fail_pedantic:
if args.exclude_optimization:
detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != DetectorClassification.OPTIMIZATION
]
if args.exclude_informational and not args.fail_pedantic:
if args.exclude_informational:
detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != DetectorClassification.INFORMATIONAL
]
if args.exclude_low and not args.fail_low:
if args.exclude_low:
detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.LOW]
if args.exclude_medium and not args.fail_medium:
if args.exclude_medium:
detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != DetectorClassification.MEDIUM
]
if args.exclude_high and not args.fail_high:
if args.exclude_high:
detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.HIGH]
if args.detectors_to_exclude:
detectors_to_run = [
@ -386,41 +387,44 @@ def parse_args(
default=defaults_flag_in_config["exclude_high"],
)
group_detector.add_argument(
fail_on_group = group_detector.add_mutually_exclusive_group()
fail_on_group.add_argument(
"--fail-pedantic",
help="Return the number of findings in the exit code",
action="store_true",
default=defaults_flag_in_config["fail_pedantic"],
help="Fail if any findings are detected",
action="store_const",
dest="fail_on",
const=FailOnLevel.PEDANTIC,
)
group_detector.add_argument(
"--no-fail-pedantic",
help="Do not return the number of findings in the exit code. Opposite of --fail-pedantic",
dest="fail_pedantic",
action="store_false",
required=False,
)
group_detector.add_argument(
fail_on_group.add_argument(
"--fail-low",
help="Fail if low or greater impact finding is detected",
action="store_true",
default=defaults_flag_in_config["fail_low"],
help="Fail if any low or greater impact findings are detected",
action="store_const",
dest="fail_on",
const=FailOnLevel.LOW,
)
group_detector.add_argument(
fail_on_group.add_argument(
"--fail-medium",
help="Fail if medium or greater impact finding is detected",
action="store_true",
default=defaults_flag_in_config["fail_medium"],
help="Fail if any medium or greater impact findings are detected",
action="store_const",
dest="fail_on",
const=FailOnLevel.MEDIUM,
)
group_detector.add_argument(
fail_on_group.add_argument(
"--fail-high",
help="Fail if high impact finding is detected",
action="store_true",
default=defaults_flag_in_config["fail_high"],
help="Fail if any high impact findings are detected",
action="store_const",
dest="fail_on",
const=FailOnLevel.HIGH,
)
fail_on_group.add_argument(
"--fail-none",
"--no-fail-pedantic",
help="Do not return the number of findings in the exit code",
action="store_const",
dest="fail_on",
const=FailOnLevel.NONE,
)
fail_on_group.set_defaults(fail_on=FailOnLevel.PEDANTIC)
group_detector.add_argument(
"--show-ignored-findings",
@ -896,17 +900,18 @@ def main_impl(
stats = pstats.Stats(cp).sort_stats("cumtime")
stats.print_stats()
if args.fail_high:
fail_on = FailOnLevel(args.fail_on)
if fail_on == FailOnLevel.HIGH:
fail_on_detection = any(result["impact"] == "High" for result in results_detectors)
elif args.fail_medium:
elif fail_on == FailOnLevel.MEDIUM:
fail_on_detection = any(
result["impact"] in ["Medium", "High"] for result in results_detectors
)
elif args.fail_low:
elif fail_on == FailOnLevel.LOW:
fail_on_detection = any(
result["impact"] in ["Low", "Medium", "High"] for result in results_detectors
)
elif args.fail_pedantic:
elif fail_on == FailOnLevel.PEDANTIC:
fail_on_detection = bool(results_detectors)
else:
fail_on_detection = False

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

@ -45,7 +45,7 @@ class Event(ContractLevel, SourceMapping):
Returns:
str: contract.func_name(type1,type2)
"""
return self.contract.name + self.full_name
return self.contract.name + "." + self.full_name
@property
def elems(self) -> List["EventVariable"]:

@ -1,10 +1,11 @@
from typing import TYPE_CHECKING, Tuple
from typing import TYPE_CHECKING, Tuple, Dict
from slither.core.declarations.top_level import TopLevel
from slither.core.declarations.contract_level import ContractLevel
from slither.core.solidity_types import Type, ElementaryType
if TYPE_CHECKING:
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations import Contract
from slither.core.scope.scope import FileScope
@ -43,6 +44,8 @@ class TypeAliasTopLevel(TypeAlias, TopLevel):
def __init__(self, underlying_type: ElementaryType, name: str, scope: "FileScope") -> None:
super().__init__(underlying_type, name)
self.file_scope: "FileScope" = scope
# operators redefined
self.operators: Dict[str, "FunctionTopLevel"] = {}
def __str__(self) -> str:
return self.name

@ -89,5 +89,6 @@ 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
from .operations.cache_array_length import CacheArrayLength
from .statements.incorrect_using_for import IncorrectUsingFor
from .operations.encode_packed import EncodePackedCollision

@ -133,7 +133,7 @@ As a result, Bob's usage of the contract is incorrect."""
continue
# Verify one of these parameters is an array in storage.
for arg in ir.arguments:
for (param, arg) in zip(ir.function.parameters, ir.arguments):
# Verify this argument is a variable that is an array type.
if not isinstance(arg, (StateVariable, LocalVariable)):
continue
@ -141,8 +141,11 @@ As a result, Bob's usage of the contract is incorrect."""
continue
# If it is a state variable OR a local variable referencing storage, we add it to the list.
if isinstance(arg, StateVariable) or (
isinstance(arg, LocalVariable) and arg.location == "storage"
if (
isinstance(arg, StateVariable)
or (isinstance(arg, LocalVariable) and arg.location == "storage")
) and (
isinstance(param.type, ArrayType) and param.location != "storage"
):
results.append((node, arg, ir.function))
return results
@ -165,9 +168,9 @@ As a result, Bob's usage of the contract is incorrect."""
calling_node.function,
" passes array ",
affected_argument,
"by reference to ",
" by reference to ",
invoked_function,
"which only takes arrays by value\n",
" which only takes arrays by value\n",
]
res = self.generate_result(info)

@ -0,0 +1,221 @@
from typing import List, Set
from slither.core.cfg.node import Node, NodeType
from slither.core.declarations import Function
from slither.core.expressions import BinaryOperation, Identifier, MemberAccess, UnaryOperation
from slither.core.solidity_types import ArrayType
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables import StateVariable
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import Length, Delete, HighLevelCall
class CacheArrayLength(AbstractDetector):
"""
Detects `for` loops that use `length` member of some storage array in their loop condition and don't modify it.
"""
ARGUMENT = "cache-array-length"
HELP = (
"Detects `for` loops that use `length` member of some storage array in their loop condition and don't "
"modify it. "
)
IMPACT = DetectorClassification.OPTIMIZATION
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#cache-array-length"
WIKI_TITLE = "Cache array length"
WIKI_DESCRIPTION = (
"Detects `for` loops that use `length` member of some storage array in their loop condition "
"and don't modify it. "
)
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract C
{
uint[] array;
function f() public
{
for (uint i = 0; i < array.length; i++)
{
// code that does not modify length of `array`
}
}
}
```
Since the `for` loop in `f` doesn't modify `array.length`, it is more gas efficient to cache it in some local variable and use that variable instead, like in the following example:
```solidity
contract C
{
uint[] array;
function f() public
{
uint array_length = array.length;
for (uint i = 0; i < array_length; i++)
{
// code that does not modify length of `array`
}
}
}
```
"""
WIKI_RECOMMENDATION = (
"Cache the lengths of storage arrays if they are used and not modified in `for` loops."
)
@staticmethod
def _is_identifier_member_access_comparison(exp: BinaryOperation) -> bool:
"""
Checks whether a BinaryOperation `exp` is an operation on Identifier and MemberAccess.
"""
return (
isinstance(exp.expression_left, Identifier)
and isinstance(exp.expression_right, MemberAccess)
) or (
isinstance(exp.expression_left, MemberAccess)
and isinstance(exp.expression_right, Identifier)
)
@staticmethod
def _extract_array_from_length_member_access(exp: MemberAccess) -> StateVariable:
"""
Given a member access `exp`, it returns state array which `length` member is accessed through `exp`.
If array is not a state array or its `length` member is not referenced, it returns `None`.
"""
if exp.member_name != "length":
return None
if not isinstance(exp.expression, Identifier):
return None
if not isinstance(exp.expression.value, StateVariable):
return None
if not isinstance(exp.expression.value.type, ArrayType):
return None
return exp.expression.value
@staticmethod
def _is_loop_referencing_array_length(
node: Node, visited: Set[Node], array: StateVariable, depth: int
) -> True:
"""
For a given loop, checks if it references `array.length` at some point.
Will also return True if `array.length` is referenced but not changed.
This may potentially generate false negatives in the detector, but it was done this way because:
- situations when array `length` is referenced but not modified in loop are rare
- checking if `array.length` is indeed modified would require much more work
"""
visited.add(node)
if node.type == NodeType.STARTLOOP:
depth += 1
if node.type == NodeType.ENDLOOP:
depth -= 1
if depth == 0:
return False
# Array length may change in the following situations:
# - when `push` is called
# - when `pop` is called
# - when `delete` is called on the entire array
# - when external function call is made (instructions from internal function calls are already in
# `node.all_slithir_operations()`, so we don't need to handle internal calls separately)
if node.type == NodeType.EXPRESSION:
for op in node.all_slithir_operations():
if isinstance(op, Length) and op.value == array:
# op accesses array.length, not necessarily modifying it
return True
if isinstance(op, Delete):
# take into account only delete entire array, since delete array[i] doesn't change `array.length`
if (
isinstance(op.expression, UnaryOperation)
and isinstance(op.expression.expression, Identifier)
and op.expression.expression.value == array
):
return True
if isinstance(op, HighLevelCall) and not op.function.view and not op.function.pure:
return True
for son in node.sons:
if son not in visited:
if CacheArrayLength._is_loop_referencing_array_length(son, visited, array, depth):
return True
return False
@staticmethod
def _handle_loops(nodes: List[Node], non_optimal_array_len_usages: List[SourceMapping]) -> None:
"""
For each loop, checks if it has a comparison with `length` array member and, if it has, checks whether that
array size could potentially change in that loop.
If it cannot, the loop condition is added to `non_optimal_array_len_usages`.
There may be some false negatives here - see docs for `_is_loop_referencing_array_length` for more information.
"""
for node in nodes:
if node.type == NodeType.STARTLOOP:
if_node = node.sons[0]
if if_node.type != NodeType.IFLOOP:
continue
if not isinstance(if_node.expression, BinaryOperation):
continue
exp: BinaryOperation = if_node.expression
if not CacheArrayLength._is_identifier_member_access_comparison(exp):
continue
array: StateVariable
if isinstance(exp.expression_right, MemberAccess):
array = CacheArrayLength._extract_array_from_length_member_access(
exp.expression_right
)
else: # isinstance(exp.expression_left, MemberAccess) == True
array = CacheArrayLength._extract_array_from_length_member_access(
exp.expression_left
)
if array is None:
continue
visited: Set[Node] = set()
if not CacheArrayLength._is_loop_referencing_array_length(
if_node, visited, array, 1
):
non_optimal_array_len_usages.append(if_node)
@staticmethod
def _get_non_optimal_array_len_usages_for_function(f: Function) -> List[SourceMapping]:
"""
Finds non-optimal usages of array length in loop conditions in a given function.
"""
non_optimal_array_len_usages: List[SourceMapping] = []
CacheArrayLength._handle_loops(f.nodes, non_optimal_array_len_usages)
return non_optimal_array_len_usages
@staticmethod
def _get_non_optimal_array_len_usages(functions: List[Function]) -> List[SourceMapping]:
"""
Finds non-optimal usages of array length in loop conditions in given functions.
"""
non_optimal_array_len_usages: List[SourceMapping] = []
for f in functions:
non_optimal_array_len_usages += (
CacheArrayLength._get_non_optimal_array_len_usages_for_function(f)
)
return non_optimal_array_len_usages
def _detect(self):
results = []
non_optimal_array_len_usages = CacheArrayLength._get_non_optimal_array_len_usages(
self.compilation_unit.functions
)
for usage in non_optimal_array_len_usages:
info = [
"Loop condition at ",
usage,
" should use cached array length instead of referencing `length` member "
"of the storage array.\n ",
]
res = self.generate_result(info)
results.append(res)
return results

@ -79,7 +79,7 @@ contract MsgValueInLoop{
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = """
Track msg.value through a local variable and decrease its amount on every iteration/usage.
Provide an explicit array of amounts alongside the receivers array, and check that the sum of all amounts matches `msg.value`.
"""
def _detect(self) -> List[Output]:

@ -47,9 +47,7 @@ class SimilarVarsDetection(AbstractDetector):
Returns:
bool: true if names are similar
"""
if len(seq1) != len(seq2):
return False
val = difflib.SequenceMatcher(a=seq1.lower(), b=seq2.lower()).ratio()
val = difflib.SequenceMatcher(a=seq1, b=seq2).ratio()
ret = val > 0.90
return ret
@ -67,19 +65,21 @@ class SimilarVarsDetection(AbstractDetector):
all_var = list(set(all_var + contract_var))
ret = []
ret = set()
# pylint: disable=consider-using-enumerate
for i in range(len(all_var)):
v1 = all_var[i]
_v1_name_lower = v1.name.lower()
for j in range(i, len(all_var)):
v2 = all_var[j]
if _v1_name_lower != v2.name.lower():
if SimilarVarsDetection.similar(v1.name, v2.name):
if (v2, v1) not in ret:
ret.append((v1, v2))
if len(v1.name) != len(v2.name):
continue
_v2_name_lower = v2.name.lower()
if _v1_name_lower != _v2_name_lower:
if SimilarVarsDetection.similar(_v1_name_lower, _v2_name_lower):
ret.add((v1, v2))
return set(ret)
return ret
def _detect(self) -> List[Output]:
"""Detect similar variables name

@ -1,6 +1,7 @@
# pylint: disable=unused-import,relative-beyond-top-level
from .summary.function import FunctionSummary
from .summary.contract import ContractSummary
from .summary.loc import LocPrinter
from .inheritance.inheritance import PrinterInheritance
from .inheritance.inheritance_graph import PrinterInheritanceGraph
from .call.call_graph import PrinterCallGraph

@ -32,7 +32,7 @@ from slither.slithir.operations import (
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
from slither.visitors.expression.constants_folding import ConstantFolding, NotConstant
def _get_name(f: Union[Function, Variable]) -> str:
@ -178,11 +178,16 @@ def _extract_constants_from_irs( # pylint: disable=too-many-branches,too-many-n
all_cst_used_in_binary[str(ir.type)].append(
ConstantValue(str(r.value), str(r.type))
)
if isinstance(ir.variable_left, Constant) and isinstance(ir.variable_right, Constant):
if ir.lvalue:
type_ = ir.lvalue.type
cst = ConstantFolding(ir.expression, type_).result()
all_cst_used.append(ConstantValue(str(cst.value), str(type_)))
if isinstance(ir.variable_left, Constant) or isinstance(
ir.variable_right, Constant
):
if ir.lvalue:
try:
type_ = ir.lvalue.type
cst = ConstantFolding(ir.expression, type_).result()
all_cst_used.append(ConstantValue(str(cst.value), str(type_)))
except NotConstant:
pass
if isinstance(ir, TypeConversion):
if isinstance(ir.variable, Constant):
if isinstance(ir.type, TypeAlias):

@ -2,12 +2,12 @@
Module printing summary of the contract
"""
import logging
from pathlib import Path
from typing import Tuple, List, Dict
from slither.core.declarations import SolidityFunction, Function
from slither.core.variables.state_variable import StateVariable
from slither.printers.abstract_printer import AbstractPrinter
from slither.printers.summary.loc import compute_loc_metrics
from slither.slithir.operations import (
LowLevelCall,
HighLevelCall,
@ -21,7 +21,6 @@ from slither.utils.colors import green, red, yellow
from slither.utils.myprettytable import MyPrettyTable
from slither.utils.standard_libraries import is_standard_library
from slither.core.cfg.node import NodeType
from slither.utils.tests_pattern import is_test_file
class PrinterHumanSummary(AbstractPrinter):
@ -32,7 +31,6 @@ class PrinterHumanSummary(AbstractPrinter):
@staticmethod
def _get_summary_erc20(contract):
functions_name = [f.name for f in contract.functions]
state_variables = [v.name for v in contract.state_variables]
@ -165,28 +163,7 @@ class PrinterHumanSummary(AbstractPrinter):
def _number_functions(contract):
return len(contract.functions)
def _lines_number(self):
if not self.slither.source_code:
return None
total_dep_lines = 0
total_lines = 0
total_tests_lines = 0
for filename, source_code in self.slither.source_code.items():
lines = len(source_code.splitlines())
is_dep = False
if self.slither.crytic_compile:
is_dep = self.slither.crytic_compile.is_dependency(filename)
if is_dep:
total_dep_lines += lines
else:
if is_test_file(Path(filename)):
total_tests_lines += lines
else:
total_lines += lines
return total_lines, total_dep_lines, total_tests_lines
def _get_number_of_assembly_lines(self):
def _get_number_of_assembly_lines(self) -> int:
total_asm_lines = 0
for contract in self.contracts:
for function in contract.functions_declared:
@ -202,9 +179,7 @@ class PrinterHumanSummary(AbstractPrinter):
return "Compilation non standard\n"
return f"Compiled with {str(self.slither.crytic_compile.type)}\n"
def _number_contracts(self):
if self.slither.crytic_compile is None:
return len(self.slither.contracts), 0, 0
def _number_contracts(self) -> Tuple[int, int, int]:
contracts = self.slither.contracts
deps = [c for c in contracts if c.is_from_dependency()]
tests = [c for c in contracts if c.is_test]
@ -226,7 +201,6 @@ class PrinterHumanSummary(AbstractPrinter):
return list(set(ercs))
def _get_features(self, contract): # pylint: disable=too-many-branches
has_payable = False
can_send_eth = False
can_selfdestruct = False
@ -291,6 +265,36 @@ class PrinterHumanSummary(AbstractPrinter):
"Proxy": contract.is_upgradeable_proxy,
}
def _get_contracts(self, txt: str) -> str:
(
number_contracts,
number_contracts_deps,
number_contracts_tests,
) = self._number_contracts()
txt += f"Total number of contracts in source files: {number_contracts}\n"
if number_contracts_deps > 0:
txt += f"Number of contracts in dependencies: {number_contracts_deps}\n"
if number_contracts_tests > 0:
txt += f"Number of contracts in tests : {number_contracts_tests}\n"
return txt
def _get_number_lines(self, txt: str, results: Dict) -> Tuple[str, Dict]:
loc = compute_loc_metrics(self.slither)
txt += "Source lines of code (SLOC) in source files: "
txt += f"{loc.src.sloc}\n"
if loc.dep.sloc > 0:
txt += "Source lines of code (SLOC) in dependencies: "
txt += f"{loc.dep.sloc}\n"
if loc.test.sloc > 0:
txt += "Source lines of code (SLOC) in tests : "
txt += f"{loc.test.sloc}\n"
results["number_lines"] = loc.src.sloc
results["number_lines__dependencies"] = loc.dep.sloc
total_asm_lines = self._get_number_of_assembly_lines()
txt += f"Number of assembly lines: {total_asm_lines}\n"
results["number_lines_assembly"] = total_asm_lines
return txt, results
def output(self, _filename): # pylint: disable=too-many-locals,too-many-statements
"""
_filename is not used
@ -311,24 +315,8 @@ class PrinterHumanSummary(AbstractPrinter):
"number_findings": {},
"detectors": [],
}
lines_number = self._lines_number()
if lines_number:
total_lines, total_dep_lines, total_tests_lines = lines_number
txt += f"Number of lines: {total_lines} (+ {total_dep_lines} in dependencies, + {total_tests_lines} in tests)\n"
results["number_lines"] = total_lines
results["number_lines__dependencies"] = total_dep_lines
total_asm_lines = self._get_number_of_assembly_lines()
txt += f"Number of assembly lines: {total_asm_lines}\n"
results["number_lines_assembly"] = total_asm_lines
(
number_contracts,
number_contracts_deps,
number_contracts_tests,
) = self._number_contracts()
txt += f"Number of contracts: {number_contracts} (+ {number_contracts_deps} in dependencies, + {number_contracts_tests} tests) \n\n"
txt = self._get_contracts(txt)
txt, results = self._get_number_lines(txt, results)
(
txt_detectors,
detectors_results,
@ -352,7 +340,7 @@ class PrinterHumanSummary(AbstractPrinter):
libs = self._standard_libraries()
if libs:
txt += f'\nUse: {", ".join(libs)}\n'
results["standard_libraries"] = [str(l) for l in libs]
results["standard_libraries"] = [str(lib) for lib in libs]
ercs = self._ercs()
if ercs:
@ -363,7 +351,6 @@ class PrinterHumanSummary(AbstractPrinter):
["Name", "# functions", "ERCS", "ERC20 info", "Complex code", "Features"]
)
for contract in self.slither.contracts_derived:
if contract.is_from_dependency() or contract.is_test:
continue

@ -0,0 +1,35 @@
"""
Lines of Code (LOC) printer
Definitions:
cloc: comment lines of code containing only comments
sloc: source lines of code with no whitespace or comments
loc: all lines of code including whitespace and comments
src: source files (excluding tests and dependencies)
dep: dependency files
test: test files
"""
from slither.printers.abstract_printer import AbstractPrinter
from slither.utils.loc import compute_loc_metrics
from slither.utils.output import Output
class LocPrinter(AbstractPrinter):
ARGUMENT = "loc"
HELP = """Count the total number lines of code (LOC), source lines of code (SLOC), \
and comment lines of code (CLOC) found in source files (SRC), dependencies (DEP), \
and test files (TEST)."""
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#loc"
def output(self, _filename: str) -> Output:
# compute loc metrics
loc = compute_loc_metrics(self.slither)
table = loc.to_pretty_table()
txt = "Lines of Code \n" + str(table)
self.info(txt)
res = self.generate_output(txt)
res.add_pretty_table(table, "Code Lines")
return res

@ -3,6 +3,7 @@ from typing import List, Union
from slither.core.declarations import SolidityVariableComposed
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.variable import Variable
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.slithir.operations.lvalue import OperationWithLValue
from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue, RVALUE, LVALUE
from slither.slithir.variables.reference import ReferenceVariable
@ -13,8 +14,10 @@ class Index(OperationWithLValue):
self, result: ReferenceVariable, left_variable: Variable, right_variable: RVALUE
) -> None:
super().__init__()
assert is_valid_lvalue(left_variable) or left_variable == SolidityVariableComposed(
"msg.data"
assert (
is_valid_lvalue(left_variable)
or left_variable == SolidityVariableComposed("msg.data")
or isinstance(left_variable, TopLevelVariable)
)
assert is_valid_rvalue(right_variable)
assert isinstance(result, ReferenceVariable)

@ -2,6 +2,7 @@ from typing import Optional, TYPE_CHECKING
from slither.core.declarations import Contract, Enum, SolidityVariable, Function
from slither.core.variables.variable import Variable
from slither.core.variables.top_level_variable import TopLevelVariable
if TYPE_CHECKING:
from slither.core.cfg.node import Node
@ -46,7 +47,7 @@ class ReferenceVariable(Variable):
from slither.slithir.utils.utils import is_valid_lvalue
assert is_valid_lvalue(points_to) or isinstance(
points_to, (SolidityVariable, Contract, Enum)
points_to, (SolidityVariable, Contract, Enum, TopLevelVariable)
)
self._points_to = points_to

@ -354,7 +354,7 @@ class FunctionSolc(CallerContextExpression):
###################################################################################
###################################################################################
def _parse_if(self, if_statement: Dict, node: NodeSolc) -> NodeSolc:
def _parse_if(self, if_statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc:
# IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )?
falseStatement = None
@ -362,21 +362,15 @@ class FunctionSolc(CallerContextExpression):
condition = if_statement["condition"]
# Note: check if the expression could be directly
# parsed here
condition_node = self._new_node(
NodeType.IF, condition["src"], node.underlying_node.scope
)
condition_node = self._new_node(NodeType.IF, condition["src"], scope)
condition_node.add_unparsed_expression(condition)
link_underlying_nodes(node, condition_node)
true_scope = Scope(
node.underlying_node.scope.is_checked, False, node.underlying_node.scope
)
true_scope = Scope(scope.is_checked, False, scope)
trueStatement = self._parse_statement(
if_statement["trueBody"], condition_node, true_scope
)
if "falseBody" in if_statement and if_statement["falseBody"]:
false_scope = Scope(
node.underlying_node.scope.is_checked, False, node.underlying_node.scope
)
false_scope = Scope(scope.is_checked, False, scope)
falseStatement = self._parse_statement(
if_statement["falseBody"], condition_node, false_scope
)
@ -385,22 +379,16 @@ class FunctionSolc(CallerContextExpression):
condition = children[0]
# Note: check if the expression could be directly
# parsed here
condition_node = self._new_node(
NodeType.IF, condition["src"], node.underlying_node.scope
)
condition_node = self._new_node(NodeType.IF, condition["src"], scope)
condition_node.add_unparsed_expression(condition)
link_underlying_nodes(node, condition_node)
true_scope = Scope(
node.underlying_node.scope.is_checked, False, node.underlying_node.scope
)
true_scope = Scope(scope.is_checked, False, scope)
trueStatement = self._parse_statement(children[1], condition_node, true_scope)
if len(children) == 3:
false_scope = Scope(
node.underlying_node.scope.is_checked, False, node.underlying_node.scope
)
false_scope = Scope(scope.is_checked, False, scope)
falseStatement = self._parse_statement(children[2], condition_node, false_scope)
endIf_node = self._new_node(NodeType.ENDIF, if_statement["src"], node.underlying_node.scope)
endIf_node = self._new_node(NodeType.ENDIF, if_statement["src"], scope)
link_underlying_nodes(trueStatement, endIf_node)
if falseStatement:
@ -409,32 +397,26 @@ class FunctionSolc(CallerContextExpression):
link_underlying_nodes(condition_node, endIf_node)
return endIf_node
def _parse_while(self, whilte_statement: Dict, node: NodeSolc) -> NodeSolc:
def _parse_while(self, whilte_statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc:
# WhileStatement = 'while' '(' Expression ')' Statement
node_startWhile = self._new_node(
NodeType.STARTLOOP, whilte_statement["src"], node.underlying_node.scope
)
node_startWhile = self._new_node(NodeType.STARTLOOP, whilte_statement["src"], scope)
body_scope = Scope(node.underlying_node.scope.is_checked, False, node.underlying_node.scope)
body_scope = Scope(scope.is_checked, False, scope)
if self.is_compact_ast:
node_condition = self._new_node(
NodeType.IFLOOP, whilte_statement["condition"]["src"], node.underlying_node.scope
NodeType.IFLOOP, whilte_statement["condition"]["src"], scope
)
node_condition.add_unparsed_expression(whilte_statement["condition"])
statement = self._parse_statement(whilte_statement["body"], node_condition, body_scope)
else:
children = whilte_statement[self.get_children("children")]
expression = children[0]
node_condition = self._new_node(
NodeType.IFLOOP, expression["src"], node.underlying_node.scope
)
node_condition = self._new_node(NodeType.IFLOOP, expression["src"], scope)
node_condition.add_unparsed_expression(expression)
statement = self._parse_statement(children[1], node_condition, body_scope)
node_endWhile = self._new_node(
NodeType.ENDLOOP, whilte_statement["src"], node.underlying_node.scope
)
node_endWhile = self._new_node(NodeType.ENDLOOP, whilte_statement["src"], scope)
link_underlying_nodes(node, node_startWhile)
link_underlying_nodes(node_startWhile, node_condition)
@ -562,7 +544,7 @@ class FunctionSolc(CallerContextExpression):
return pre, cond, post, body
def _parse_for(self, statement: Dict, node: NodeSolc) -> NodeSolc:
def _parse_for(self, statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc:
# ForStatement = 'for' '(' (SimpleStatement)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement
if self.is_compact_ast:
@ -570,17 +552,13 @@ class FunctionSolc(CallerContextExpression):
else:
pre, cond, post, body = self._parse_for_legacy_ast(statement)
node_startLoop = self._new_node(
NodeType.STARTLOOP, statement["src"], node.underlying_node.scope
)
node_endLoop = self._new_node(
NodeType.ENDLOOP, statement["src"], node.underlying_node.scope
)
node_startLoop = self._new_node(NodeType.STARTLOOP, statement["src"], scope)
node_endLoop = self._new_node(NodeType.ENDLOOP, statement["src"], scope)
last_scope = node.underlying_node.scope
last_scope = scope
if pre:
pre_scope = Scope(node.underlying_node.scope.is_checked, False, last_scope)
pre_scope = Scope(scope.is_checked, False, last_scope)
last_scope = pre_scope
node_init_expression = self._parse_statement(pre, node, pre_scope)
link_underlying_nodes(node_init_expression, node_startLoop)
@ -588,7 +566,7 @@ class FunctionSolc(CallerContextExpression):
link_underlying_nodes(node, node_startLoop)
if cond:
cond_scope = Scope(node.underlying_node.scope.is_checked, False, last_scope)
cond_scope = Scope(scope.is_checked, False, last_scope)
last_scope = cond_scope
node_condition = self._new_node(NodeType.IFLOOP, cond["src"], cond_scope)
node_condition.add_unparsed_expression(cond)
@ -599,7 +577,7 @@ class FunctionSolc(CallerContextExpression):
node_condition = None
node_beforeBody = node_startLoop
body_scope = Scope(node.underlying_node.scope.is_checked, False, last_scope)
body_scope = Scope(scope.is_checked, False, last_scope)
last_scope = body_scope
node_body = self._parse_statement(body, node_beforeBody, body_scope)
@ -619,14 +597,10 @@ class FunctionSolc(CallerContextExpression):
return node_endLoop
def _parse_dowhile(self, do_while_statement: Dict, node: NodeSolc) -> NodeSolc:
def _parse_dowhile(self, do_while_statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc:
node_startDoWhile = self._new_node(
NodeType.STARTLOOP, do_while_statement["src"], node.underlying_node.scope
)
condition_scope = Scope(
node.underlying_node.scope.is_checked, False, node.underlying_node.scope
)
node_startDoWhile = self._new_node(NodeType.STARTLOOP, do_while_statement["src"], scope)
condition_scope = Scope(scope.is_checked, False, scope)
if self.is_compact_ast:
node_condition = self._new_node(
@ -644,7 +618,7 @@ class FunctionSolc(CallerContextExpression):
node_condition.add_unparsed_expression(expression)
statement = self._parse_statement(children[1], node_condition, condition_scope)
body_scope = Scope(node.underlying_node.scope.is_checked, False, condition_scope)
body_scope = Scope(scope.is_checked, False, condition_scope)
node_endDoWhile = self._new_node(NodeType.ENDLOOP, do_while_statement["src"], body_scope)
link_underlying_nodes(node, node_startDoWhile)
@ -724,14 +698,12 @@ class FunctionSolc(CallerContextExpression):
return ret
def _parse_try_catch(self, statement: Dict, node: NodeSolc) -> NodeSolc:
def _parse_try_catch(self, statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc:
externalCall = statement.get("externalCall", None)
if externalCall is None:
raise ParsingError(f"Try/Catch not correctly parsed by Slither {statement}")
catch_scope = Scope(
node.underlying_node.scope.is_checked, False, node.underlying_node.scope
)
catch_scope = Scope(scope.is_checked, False, scope)
new_node = self._new_node(NodeType.TRY, statement["src"], catch_scope)
clauses = statement.get("clauses", [])
# the first clause is the try scope
@ -748,18 +720,20 @@ class FunctionSolc(CallerContextExpression):
# we set the parameters (e.g. data in this case. catch(string memory data) ...)
# to be initialized so they are not reported by the uninitialized-local-variables detector
if index >= 1:
self._parse_catch(clause, node, True)
self._parse_catch(clause, node, catch_scope, True)
else:
# the parameters for the try scope were already added in _construct_try_expression
self._parse_catch(clause, node, False)
self._parse_catch(clause, node, catch_scope, False)
return node
def _parse_catch(self, statement: Dict, node: NodeSolc, add_param: bool) -> NodeSolc:
def _parse_catch(
self, statement: Dict, node: NodeSolc, scope: Scope, add_param: bool
) -> NodeSolc:
block = statement.get("block", None)
if block is None:
raise ParsingError(f"Catch not correctly parsed by Slither {statement}")
try_scope = Scope(node.underlying_node.scope.is_checked, False, node.underlying_node.scope)
try_scope = Scope(scope.is_checked, False, scope)
try_node = self._new_node(NodeType.CATCH, statement["src"], try_scope)
link_underlying_nodes(node, try_node)
@ -777,7 +751,7 @@ class FunctionSolc(CallerContextExpression):
return self._parse_statement(block, try_node, try_scope)
def _parse_variable_definition(self, statement: Dict, node: NodeSolc) -> NodeSolc:
def _parse_variable_definition(self, statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc:
try:
local_var = LocalVariable()
local_var.set_function(self._function)
@ -787,9 +761,7 @@ class FunctionSolc(CallerContextExpression):
self._add_local_variable(local_var_parser)
# local_var.analyze(self)
new_node = self._new_node(
NodeType.VARIABLE, statement["src"], node.underlying_node.scope
)
new_node = self._new_node(NodeType.VARIABLE, statement["src"], scope)
new_node.underlying_node.add_variable_declaration(local_var)
link_underlying_nodes(node, new_node)
return new_node
@ -819,7 +791,7 @@ class FunctionSolc(CallerContextExpression):
"declarations": [variable],
"initialValue": init,
}
new_node = self._parse_variable_definition(new_statement, new_node)
new_node = self._parse_variable_definition(new_statement, new_node, scope)
else:
# If we have
@ -841,7 +813,7 @@ class FunctionSolc(CallerContextExpression):
variables.append(variable)
new_node = self._parse_variable_definition_init_tuple(
new_statement, i, new_node
new_statement, i, new_node, scope
)
i = i + 1
@ -873,9 +845,7 @@ class FunctionSolc(CallerContextExpression):
"typeDescriptions": {"typeString": "tuple()"},
}
node = new_node
new_node = self._new_node(
NodeType.EXPRESSION, statement["src"], node.underlying_node.scope
)
new_node = self._new_node(NodeType.EXPRESSION, statement["src"], scope)
new_node.add_unparsed_expression(expression)
link_underlying_nodes(node, new_node)
@ -906,7 +876,7 @@ class FunctionSolc(CallerContextExpression):
self.get_children("children"): [variable, init],
}
new_node = self._parse_variable_definition(new_statement, new_node)
new_node = self._parse_variable_definition(new_statement, new_node, scope)
else:
# If we have
# var (a, b) = f()
@ -925,7 +895,7 @@ class FunctionSolc(CallerContextExpression):
variables.append(variable)
new_node = self._parse_variable_definition_init_tuple(
new_statement, i, new_node
new_statement, i, new_node, scope
)
i = i + 1
var_identifiers = []
@ -955,16 +925,14 @@ class FunctionSolc(CallerContextExpression):
],
}
node = new_node
new_node = self._new_node(
NodeType.EXPRESSION, statement["src"], node.underlying_node.scope
)
new_node = self._new_node(NodeType.EXPRESSION, statement["src"], scope)
new_node.add_unparsed_expression(expression)
link_underlying_nodes(node, new_node)
return new_node
def _parse_variable_definition_init_tuple(
self, statement: Dict, index: int, node: NodeSolc
self, statement: Dict, index: int, node: NodeSolc, scope
) -> NodeSolc:
local_var = LocalVariableInitFromTuple()
local_var.set_function(self._function)
@ -974,7 +942,7 @@ class FunctionSolc(CallerContextExpression):
self._add_local_variable(local_var_parser)
new_node = self._new_node(NodeType.VARIABLE, statement["src"], node.underlying_node.scope)
new_node = self._new_node(NodeType.VARIABLE, statement["src"], scope)
new_node.underlying_node.add_variable_declaration(local_var)
link_underlying_nodes(node, new_node)
return new_node
@ -995,15 +963,15 @@ class FunctionSolc(CallerContextExpression):
name = statement[self.get_key()]
# SimpleStatement = VariableDefinition | ExpressionStatement
if name == "IfStatement":
node = self._parse_if(statement, node)
node = self._parse_if(statement, node, scope)
elif name == "WhileStatement":
node = self._parse_while(statement, node)
node = self._parse_while(statement, node, scope)
elif name == "ForStatement":
node = self._parse_for(statement, node)
node = self._parse_for(statement, node, scope)
elif name == "Block":
node = self._parse_block(statement, node)
node = self._parse_block(statement, node, scope)
elif name == "UncheckedBlock":
node = self._parse_unchecked_block(statement, node)
node = self._parse_unchecked_block(statement, node, scope)
elif name == "InlineAssembly":
# Added with solc 0.6 - the yul code is an AST
if "AST" in statement and not self.compilation_unit.core.skip_assembly:
@ -1025,7 +993,7 @@ class FunctionSolc(CallerContextExpression):
link_underlying_nodes(node, asm_node)
node = asm_node
elif name == "DoWhileStatement":
node = self._parse_dowhile(statement, node)
node = self._parse_dowhile(statement, node, scope)
# For Continue / Break / Return / Throw
# The is fixed later
elif name == "Continue":
@ -1066,7 +1034,7 @@ class FunctionSolc(CallerContextExpression):
link_underlying_nodes(node, new_node)
node = new_node
elif name in ["VariableDefinitionStatement", "VariableDeclarationStatement"]:
node = self._parse_variable_definition(statement, node)
node = self._parse_variable_definition(statement, node, scope)
elif name == "ExpressionStatement":
# assert len(statement[self.get_children('expression')]) == 1
# assert not 'attributes' in statement
@ -1080,7 +1048,7 @@ class FunctionSolc(CallerContextExpression):
link_underlying_nodes(node, new_node)
node = new_node
elif name == "TryStatement":
node = self._parse_try_catch(statement, node)
node = self._parse_try_catch(statement, node, scope)
# elif name == 'TryCatchClause':
# self._parse_catch(statement, node)
elif name == "RevertStatement":
@ -1097,7 +1065,7 @@ class FunctionSolc(CallerContextExpression):
return node
def _parse_block(self, block: Dict, node: NodeSolc, check_arithmetic: bool = False) -> NodeSolc:
def _parse_block(self, block: Dict, node: NodeSolc, scope: Scope) -> NodeSolc:
"""
Return:
Node
@ -1109,13 +1077,12 @@ class FunctionSolc(CallerContextExpression):
else:
statements = block[self.get_children("children")]
check_arithmetic = check_arithmetic | node.underlying_node.scope.is_checked
new_scope = Scope(check_arithmetic, False, node.underlying_node.scope)
new_scope = Scope(scope.is_checked, False, scope)
for statement in statements:
node = self._parse_statement(statement, node, new_scope)
return node
def _parse_unchecked_block(self, block: Dict, node: NodeSolc):
def _parse_unchecked_block(self, block: Dict, node: NodeSolc, scope):
"""
Return:
Node
@ -1127,7 +1094,8 @@ class FunctionSolc(CallerContextExpression):
else:
statements = block[self.get_children("children")]
new_scope = Scope(False, False, node.underlying_node.scope)
new_scope = Scope(False, False, scope)
for statement in statements:
node = self._parse_statement(statement, node, new_scope)
return node
@ -1148,8 +1116,7 @@ class FunctionSolc(CallerContextExpression):
self._function.is_empty = True
else:
self._function.is_empty = False
check_arithmetic = self.compilation_unit.solc_version >= "0.8.0"
self._parse_block(cfg, node, check_arithmetic=check_arithmetic)
self._parse_block(cfg, node, self.underlying_function)
self._remove_incorrect_edges()
self._remove_alone_endif()

@ -55,22 +55,29 @@ class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few-
self._propagate_global(type_name)
else:
for f in self._functions:
full_name_split = f["function"]["name"].split(".")
if len(full_name_split) == 1:
# User defined operator
if "operator" in f:
# Top level function
function_name: str = full_name_split[0]
self._analyze_top_level_function(function_name, type_name)
elif len(full_name_split) == 2:
# It can be a top level function behind an aliased import
# or a library function
first_part = full_name_split[0]
function_name = full_name_split[1]
self._check_aliased_import(first_part, function_name, type_name)
function_name: str = f["definition"]["name"]
operator: str = f["operator"]
self._analyze_operator(operator, function_name, type_name)
else:
# MyImport.MyLib.a we don't care of the alias
library_name_str = full_name_split[1]
function_name = full_name_split[2]
self._analyze_library_function(library_name_str, function_name, type_name)
full_name_split = f["function"]["name"].split(".")
if len(full_name_split) == 1:
# Top level function
function_name: str = full_name_split[0]
self._analyze_top_level_function(function_name, type_name)
elif len(full_name_split) == 2:
# It can be a top level function behind an aliased import
# or a library function
first_part = full_name_split[0]
function_name = full_name_split[1]
self._check_aliased_import(first_part, function_name, type_name)
else:
# MyImport.MyLib.a we don't care of the alias
library_name_str = full_name_split[1]
function_name = full_name_split[2]
self._analyze_library_function(library_name_str, function_name, type_name)
def _check_aliased_import(
self,
@ -101,6 +108,19 @@ class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few-
self._propagate_global(type_name)
break
def _analyze_operator(
self, operator: str, function_name: str, type_name: TypeAliasTopLevel
) -> None:
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
):
type_name.operators[operator] = tl_function
break
def _analyze_library_function(
self,
library_name: str,

@ -1,6 +1,6 @@
import logging
import re
from typing import Union, Dict, TYPE_CHECKING
from typing import Union, Dict, TYPE_CHECKING, List, Any
import slither.core.expressions.type_conversion
from slither.core.declarations.solidity_variables import (
@ -236,6 +236,24 @@ if TYPE_CHECKING:
pass
def _user_defined_op_call(
caller_context: CallerContextExpression, src, function_id: int, args: List[Any], type_call: str
) -> CallExpression:
var, was_created = find_variable(None, caller_context, function_id)
if was_created:
var.set_offset(src, caller_context.compilation_unit)
identifier = Identifier(var)
identifier.set_offset(src, caller_context.compilation_unit)
var.references.append(identifier.source_mapping)
call = CallExpression(identifier, args, type_call)
call.set_offset(src, caller_context.compilation_unit)
return call
def parse_expression(expression: Dict, caller_context: CallerContextExpression) -> "Expression":
# pylint: disable=too-many-nested-blocks,too-many-statements
"""
@ -274,16 +292,24 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
if name == "UnaryOperation":
if is_compact_ast:
attributes = expression
else:
attributes = expression["attributes"]
assert "prefix" in attributes
operation_type = UnaryOperationType.get_type(attributes["operator"], attributes["prefix"])
if is_compact_ast:
expression = parse_expression(expression["subExpression"], caller_context)
else:
attributes = expression["attributes"]
assert len(expression["children"]) == 1
expression = parse_expression(expression["children"][0], caller_context)
assert "prefix" in attributes
# Use of user defined operation
if "function" in attributes:
return _user_defined_op_call(
caller_context,
src,
attributes["function"],
[expression],
attributes["typeDescriptions"]["typeString"],
)
operation_type = UnaryOperationType.get_type(attributes["operator"], attributes["prefix"])
unary_op = UnaryOperation(expression, operation_type)
unary_op.set_offset(src, caller_context.compilation_unit)
return unary_op
@ -291,17 +317,25 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
if name == "BinaryOperation":
if is_compact_ast:
attributes = expression
else:
attributes = expression["attributes"]
operation_type = BinaryOperationType.get_type(attributes["operator"])
if is_compact_ast:
left_expression = parse_expression(expression["leftExpression"], caller_context)
right_expression = parse_expression(expression["rightExpression"], caller_context)
else:
assert len(expression["children"]) == 2
attributes = expression["attributes"]
left_expression = parse_expression(expression["children"][0], caller_context)
right_expression = parse_expression(expression["children"][1], caller_context)
# Use of user defined operation
if "function" in attributes:
return _user_defined_op_call(
caller_context,
src,
attributes["function"],
[left_expression, right_expression],
attributes["typeDescriptions"]["typeString"],
)
operation_type = BinaryOperationType.get_type(attributes["operator"])
binary_op = BinaryOperation(left_expression, right_expression, operation_type)
binary_op.set_offset(src, caller_context.compilation_unit)
return binary_op
@ -433,6 +467,8 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
type_candidate = ElementaryType("uint256")
else:
type_candidate = ElementaryType("string")
elif type_candidate.startswith("rational_const "):
type_candidate = ElementaryType("uint256")
elif type_candidate.startswith("int_const "):
type_candidate = ElementaryType("uint256")
elif type_candidate.startswith("bool"):

@ -16,3 +16,21 @@ class LocalVariableInitFromTupleSolc(VariableDeclarationSolc):
# Todo: Not sure how to overcome this with mypy
assert isinstance(self._variable, LocalVariableInitFromTuple)
return self._variable
def _analyze_variable_attributes(self, attributes: Dict) -> None:
"""'
Variable Location
Can be storage/memory or default
"""
if "storageLocation" in attributes:
location = attributes["storageLocation"]
self.underlying_variable.set_location(location)
else:
if "memory" in attributes["type"]:
self.underlying_variable.set_location("memory")
elif "storage" in attributes["type"]:
self.underlying_variable.set_location("storage")
else:
self.underlying_variable.set_location("default")
super()._analyze_variable_attributes(attributes)

@ -0,0 +1,21 @@
# Slither-interface
Generates code for a Solidity interface from contract
## Usage
Run `slither-interface <ContractName> <source file or deployment address>`.
## CLI Interface
```shell
positional arguments:
contract_source The name of the contract (case sensitive) followed by the deployed contract address if verified on etherscan or project directory/filename for local contracts.
optional arguments:
-h, --help show this help message and exit
--unroll-structs Whether to use structures' underlying types instead of the user-defined type
--exclude-events Excludes event signatures in the interface
--exclude-errors Excludes custom error signatures in the interface
--exclude-enums Excludes enum definitions in the interface
--exclude-structs Exclude struct definitions in the interface
```

@ -86,10 +86,11 @@ def main() -> None:
)
# add version pragma
interface = (
f"pragma solidity {_contract.compilation_unit.pragma_directives[0].version};\n\n"
+ interface
)
if _contract.compilation_unit.pragma_directives:
interface = (
f"pragma solidity {_contract.compilation_unit.pragma_directives[0].version};\n\n"
+ interface
)
# write interface to file
export = Path("crytic-export", "interfaces")

@ -8,20 +8,29 @@ Slither-read-storage is a tool to retrieve the storage slots and values of entir
```shell
positional arguments:
contract_source (DIR) ADDRESS The deployed contract address if verified on etherscan. Prepend project directory for unverified contracts.
contract_source The deployed contract address if verified on etherscan. Prepend project directory for unverified contracts.
optional arguments:
--variable-name VARIABLE_NAME The name of the variable whose value will be returned.
--rpc-url RPC_URL An endpoint for web3 requests.
--key KEY The key/ index whose value will be returned from a mapping or array.
--deep-key DEEP_KEY The key/ index whose value will be returned from a deep mapping or multidimensional array.
--struct-var STRUCT_VAR The name of the variable whose value will be returned from a struct.
--storage-address STORAGE_ADDRESS The address of the storage contract (if a proxy pattern is used).
--contract-name CONTRACT_NAME The name of the logic contract.
--json FILE Write the entire storage layout in JSON format to the specified FILE
--value Toggle used to include values in output.
--max-depth MAX_DEPTH Max depth to search in data structure.
--block BLOCK_NUMBER Block number to retrieve storage from (requires archive rpc node)
-h, --help show this help message and exit
--variable-name VARIABLE_NAME
The name of the variable whose value will be returned.
--rpc-url RPC_URL An endpoint for web3 requests.
--key KEY The key/ index whose value will be returned from a mapping or array.
--deep-key DEEP_KEY The key/ index whose value will be returned from a deep mapping or multidimensional array.
--struct-var STRUCT_VAR
The name of the variable whose value will be returned from a struct.
--storage-address STORAGE_ADDRESS
The address of the storage contract (if a proxy pattern is used).
--contract-name CONTRACT_NAME
The name of the logic contract.
--json JSON Save the result in a JSON file.
--value Toggle used to include values in output.
--table Print table view of storage layout
--silent Silence log outputs
--max-depth MAX_DEPTH
Max depth to search in data structure.
--block BLOCK The block number to read storage from. Requires an archive node to be provided as the RPC url.
--unstructured Include unstructured storage slots
```
### Examples

@ -104,6 +104,12 @@ def parse_args() -> argparse.Namespace:
default="latest",
)
parser.add_argument(
"--unstructured",
action="store_true",
help="Include unstructured storage slots",
)
cryticparser.init(parser)
return parser.parse_args()
@ -133,6 +139,7 @@ def main() -> None:
rpc_info = RpcInfo(args.rpc_url, block)
srs = SlitherReadStorage(contracts, args.max_depth, rpc_info)
srs.unstructured = bool(args.unstructured)
# Remove target prefix e.g. rinkeby:0x0 -> 0x0.
address = target[target.find(":") + 1 :]
# Default to implementation address unless a storage address is given.

@ -15,9 +15,21 @@ from web3.middleware import geth_poa_middleware
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.cfg.node import NodeType
from slither.core.variables.state_variable import StateVariable
from slither.core.variables.structure_variable import StructureVariable
from slither.core.expressions import (
AssignmentOperation,
Literal,
Identifier,
BinaryOperation,
UnaryOperation,
TupleExpression,
TypeConversion,
CallExpression,
)
from slither.utils.myprettytable import MyPrettyTable
from slither.visitors.expression.constants_folding import ConstantFolding, NotConstant
from .utils import coerce_type, get_offset_value, get_storage_data
@ -72,7 +84,7 @@ class RpcInfo:
return self._block
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-instance-attributes,too-many-public-methods
class SlitherReadStorage:
def __init__(self, contracts: List[Contract], max_depth: int, rpc_info: RpcInfo = None) -> None:
self._checksum_address: Optional[ChecksumAddress] = None
@ -81,9 +93,11 @@ class SlitherReadStorage:
self._max_depth: int = max_depth
self._slot_info: Dict[str, SlotInfo] = {}
self._target_variables: List[Tuple[Contract, StateVariable]] = []
self._constant_storage_slots: List[Tuple[Contract, StateVariable]] = []
self.rpc_info: Optional[RpcInfo] = rpc_info
self.storage_address: Optional[str] = None
self.table: Optional[MyPrettyTable] = None
self.unstructured: bool = False
@property
def contracts(self) -> List[Contract]:
@ -114,6 +128,11 @@ class SlitherReadStorage:
"""Storage variables (not constant or immutable) and their associated contract."""
return self._target_variables
@property
def constant_slots(self) -> List[Tuple[Contract, StateVariable]]:
"""Constant bytes32 variables and their associated contract."""
return self._constant_storage_slots
@property
def slot_info(self) -> Dict[str, SlotInfo]:
"""Contains the location, type, size, offset, and value of contract slots."""
@ -133,9 +152,48 @@ class SlitherReadStorage:
elif isinstance(type_, ArrayType):
elems = self._all_array_slots(var, contract, type_, info.slot)
tmp[var.name].elems = elems
if self.unstructured:
tmp.update(self.get_unstructured_layout())
self._slot_info = tmp
def get_unstructured_layout(self) -> Dict[str, SlotInfo]:
tmp: Dict[str, SlotInfo] = {}
for _, var in self.constant_slots:
var_name = var.name
try:
exp = var.expression
if isinstance(
exp,
(
BinaryOperation,
UnaryOperation,
Identifier,
TupleExpression,
TypeConversion,
CallExpression,
),
):
exp = ConstantFolding(exp, "bytes32").result()
if isinstance(exp, Literal):
slot = coerce_type("int", exp.value)
else:
continue
offset = 0
type_string, size = self.find_constant_slot_storage_type(var)
if type_string:
tmp[var.name] = SlotInfo(
name=var_name, type_string=type_string, slot=slot, size=size, offset=offset
)
self.log += (
f"\nSlot Name: {var_name}\nType: bytes32"
f"\nStorage Type: {type_string}\nSlot: {str(exp)}\n"
)
logger.info(self.log)
self.log = ""
except NotConstant:
continue
return tmp
# TODO: remove this pylint exception (montyly)
# pylint: disable=too-many-locals
def get_storage_slot(
@ -144,7 +202,8 @@ class SlitherReadStorage:
contract: Contract,
**kwargs: Any,
) -> Union[SlotInfo, None]:
"""Finds the storage slot of a variable in a given contract.
"""
Finds the storage slot of a variable in a given contract.
Args:
target_variable (`StateVariable`): The variable to retrieve the slot for.
contracts (`Contract`): The contract that contains the given state variable.
@ -230,6 +289,78 @@ class SlitherReadStorage:
if slot_info:
self._slot_info[f"{contract.name}.{var.name}"] = slot_info
def find_constant_slot_storage_type(
self, var: StateVariable
) -> Tuple[Optional[str], Optional[int]]:
"""
Given a constant bytes32 StateVariable, tries to determine which variable type is stored there, using the
heuristic that if a function reads from the slot and returns a value, it probably stores that type of value.
Also uses the StorageSlot library as a heuristic when a function has no return but uses the library's getters.
Args:
var (StateVariable): The constant bytes32 storage slot.
Returns:
type (str): The type of value stored in the slot.
size (int): The type's size in bits.
"""
assert var.is_constant and var.type == ElementaryType("bytes32")
storage_type = None
size = None
funcs = []
for c in self.contracts:
c_funcs = c.get_functions_reading_from_variable(var)
c_funcs.extend(
f
for f in c.functions
if any(str(v.expression) == str(var.expression) for v in f.variables)
)
c_funcs = list(set(c_funcs))
funcs.extend(c_funcs)
fallback = [f for f in var.contract.functions if f.is_fallback]
funcs += fallback
for func in funcs:
rets = func.return_type if func.return_type is not None else []
for ret in rets:
size, _ = ret.storage_size
if size <= 32:
return str(ret), size * 8
for node in func.all_nodes():
exp = node.expression
# Look for use of the common OpenZeppelin StorageSlot library
if f"getAddressSlot({var.name})" in str(exp):
return "address", 160
if f"getBooleanSlot({var.name})" in str(exp):
return "bool", 1
if f"getBytes32Slot({var.name})" in str(exp):
return "bytes32", 256
if f"getUint256Slot({var.name})" in str(exp):
return "uint256", 256
# Look for variable assignment in assembly loaded from a hardcoded slot
if (
isinstance(exp, AssignmentOperation)
and isinstance(exp.expression_left, Identifier)
and isinstance(exp.expression_right, CallExpression)
and "sload" in str(exp.expression_right.called)
and str(exp.expression_right.arguments[0]) == str(var.expression)
):
if func.is_fallback:
return "address", 160
storage_type = exp.expression_left.value.type.name
size, _ = exp.expression_left.value.type.storage_size
return storage_type, size * 8
# Look for variable storage in assembly stored to a hardcoded slot
if (
isinstance(exp, CallExpression)
and "sstore" in str(exp.called)
and isinstance(exp.arguments[0], Identifier)
and isinstance(exp.arguments[1], Identifier)
and str(exp.arguments[0].value.expression) == str(var.expression)
):
storage_type = exp.arguments[1].value.type.name
size, _ = exp.arguments[1].value.type.storage_size
return storage_type, size * 8
return storage_type, size
def walk_slot_info(self, func: Callable) -> None:
stack = list(self.slot_info.values())
while stack:
@ -242,7 +373,8 @@ class SlitherReadStorage:
func(slot_info)
def get_slot_values(self, slot_info: SlotInfo) -> None:
"""Fetches the slot value of `SlotInfo` object
"""
Fetches the slot value of `SlotInfo` object
:param slot_info:
"""
assert self.rpc_info is not None
@ -257,25 +389,162 @@ class SlitherReadStorage:
)
logger.info(f"\nValue: {slot_info.value}\n")
def get_all_storage_variables(self, func: Callable = None) -> None:
"""Fetches all storage variables from a list of contracts.
def get_all_storage_variables(self, func: Callable = lambda x: x) -> None:
"""
Fetches all storage variables from a list of contracts.
kwargs:
func (Callable, optional): A criteria to filter functions e.g. name.
"""
for contract in self.contracts:
self._target_variables.extend(
filter(
func,
[
(contract, var)
for var in contract.state_variables_ordered
if not var.is_constant and not var.is_immutable
],
)
for var in contract.state_variables_ordered:
if func(var):
if not var.is_constant and not var.is_immutable:
self._target_variables.append((contract, var))
elif (
self.unstructured
and var.is_constant
and var.type == ElementaryType("bytes32")
):
self._constant_storage_slots.append((contract, var))
if self.unstructured:
hardcoded_slot = self.find_hardcoded_slot_in_fallback(contract)
if hardcoded_slot is not None:
self._constant_storage_slots.append((contract, hardcoded_slot))
def find_hardcoded_slot_in_fallback(self, contract: Contract) -> Optional[StateVariable]:
"""
Searches the contract's fallback function for a sload from a literal storage slot, i.e.,
`let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7)`.
Args:
contract: a Contract object, which should have a fallback function.
Returns:
A newly created StateVariable representing the Literal bytes32 slot, if one is found, otherwise None.
"""
fallback = None
for func in contract.functions_entry_points:
if func.is_fallback:
fallback = func
break
if fallback is None:
return None
queue = [fallback.entry_point]
visited = []
while len(queue) > 0:
node = queue.pop(0)
visited.append(node)
queue.extend(son for son in node.sons if son not in visited)
if node.type == NodeType.ASSEMBLY and isinstance(node.inline_asm, str):
return SlitherReadStorage.find_hardcoded_slot_in_asm_str(node.inline_asm, contract)
if node.type == NodeType.EXPRESSION:
sv = self.find_hardcoded_slot_in_exp(node.expression, contract)
if sv is not None:
return sv
return None
@staticmethod
def find_hardcoded_slot_in_asm_str(
inline_asm: str, contract: Contract
) -> Optional[StateVariable]:
"""
Searches a block of assembly code (given as a string) for a sload from a literal storage slot.
Does not work if the argument passed to sload does not start with "0x", i.e., `sload(add(1,1))`
or `and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff)`.
Args:
inline_asm: a string containing all the code in an assembly node (node.inline_asm for solc < 0.6.0).
Returns:
A newly created StateVariable representing the Literal bytes32 slot, if one is found, otherwise None.
"""
asm_split = inline_asm.split("\n")
for asm in asm_split:
if "sload(" in asm: # Only handle literals
arg = asm.split("sload(")[1].split(")")[0]
if arg.startswith("0x"):
exp = Literal(arg, ElementaryType("bytes32"))
sv = StateVariable()
sv.name = "fallback_sload_hardcoded"
sv.expression = exp
sv.is_constant = True
sv.type = exp.type
sv.set_contract(contract)
return sv
return None
def find_hardcoded_slot_in_exp(
self, exp: "Expression", contract: Contract
) -> Optional[StateVariable]:
"""
Parses an expression to see if it contains a sload from a literal storage slot,
unrolling nested expressions if necessary to determine which slot it loads from.
Args:
exp: an Expression object to search.
contract: the Contract containing exp.
Returns:
A newly created StateVariable representing the Literal bytes32 slot, if one is found, otherwise None.
"""
if isinstance(exp, AssignmentOperation):
exp = exp.expression_right
while isinstance(exp, BinaryOperation):
exp = next(
(e for e in exp.expressions if isinstance(e, (CallExpression, BinaryOperation))),
exp.expression_left,
)
while isinstance(exp, CallExpression) and len(exp.arguments) > 0:
called = exp.called
exp = exp.arguments[0]
if "sload" in str(called):
break
if isinstance(
exp,
(
BinaryOperation,
UnaryOperation,
Identifier,
TupleExpression,
TypeConversion,
CallExpression,
),
):
try:
exp = ConstantFolding(exp, "bytes32").result()
except NotConstant:
return None
if (
isinstance(exp, Literal)
and isinstance(exp.type, ElementaryType)
and exp.type.name in ["bytes32", "uint256"]
):
sv = StateVariable()
sv.name = "fallback_sload_hardcoded"
value = exp.value
str_value = str(value)
if str_value.isdecimal():
value = int(value)
if isinstance(value, (int, bytes)):
if isinstance(value, bytes):
str_value = "0x" + value.hex()
value = int(str_value, 16)
exp = Literal(str_value, ElementaryType("bytes32"))
state_var_slots = [
self.get_variable_info(contract, var)[0]
for contract, var in self.target_variables
]
if value in state_var_slots:
return None
sv.expression = exp
sv.is_constant = True
sv.type = ElementaryType("bytes32")
sv.set_contract(contract)
return sv
return None
def convert_slot_info_to_rows(self, slot_info: SlotInfo) -> None:
"""Convert and append slot info to table. Create table if it
"""
Convert and append slot info to table. Create table if it
does not yet exist
:param slot_info:
"""
@ -293,7 +562,8 @@ class SlitherReadStorage:
def _find_struct_var_slot(
elems: List[StructureVariable], slot_as_bytes: bytes, struct_var: str
) -> Tuple[str, str, bytes, int, int]:
"""Finds the slot of a structure variable.
"""
Finds the slot of a structure variable.
Args:
elems (List[StructureVariable]): Ordered list of structure variables.
slot_as_bytes (bytes): The slot of the struct to begin searching at.
@ -335,7 +605,8 @@ class SlitherReadStorage:
deep_key: int = None,
struct_var: str = None,
) -> Tuple[str, str, bytes, int, int]:
"""Finds the slot of array's index.
"""
Finds the slot of array's index.
Args:
target_variable (`StateVariable`): The array that contains the target variable.
slot (bytes): The starting slot of the array.
@ -438,7 +709,8 @@ class SlitherReadStorage:
deep_key: Union[int, str] = None,
struct_var: str = None,
) -> Tuple[str, str, bytes, int, int]:
"""Finds the data slot of a target variable within a mapping.
"""
Finds the data slot of a target variable within a mapping.
target_variable (`StateVariable`): The mapping that contains the target variable.
slot (bytes): The starting slot of the mapping.
key (Union[int, str]): The key the variable is stored at.
@ -509,7 +781,7 @@ class SlitherReadStorage:
)
info += info_tmp
# TODO: suppory mapping with dynamic arrays
# TODO: support mapping with dynamic arrays
# mapping(elem => elem)
elif isinstance(target_variable_type.type_to, ElementaryType):
@ -615,7 +887,8 @@ class SlitherReadStorage:
return elems
def _get_array_length(self, type_: Type, slot: int) -> int:
"""Gets the length of dynamic and fixed arrays.
"""
Gets the length of dynamic and fixed arrays.
Args:
type_ (`AbstractType`): The array type.
slot (int): Slot a dynamic array's length is stored at.

@ -37,6 +37,8 @@ def coerce_type(
(Union[int, bool, str, ChecksumAddress, hex]): The type representation of the value.
"""
if "int" in solidity_type:
if str(value).startswith("0x"):
return to_int(hexstr=value)
return to_int(value)
if "bool" in solidity_type:
return bool(to_int(value))

@ -1,4 +1,5 @@
import argparse
import enum
import json
import os
import re
@ -27,6 +28,15 @@ JSON_OUTPUT_TYPES = [
"list-printers",
]
class FailOnLevel(enum.Enum):
PEDANTIC = "pedantic"
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
NONE = "none"
# Those are the flags shared by the command line and the config file
defaults_flag_in_config = {
"codex": False,
@ -44,10 +54,7 @@ defaults_flag_in_config = {
"exclude_low": False,
"exclude_medium": False,
"exclude_high": False,
"fail_pedantic": True,
"fail_low": False,
"fail_medium": False,
"fail_high": False,
"fail_on": FailOnLevel.PEDANTIC,
"json": None,
"sarif": None,
"json-types": ",".join(DEFAULT_JSON_OUTPUT_TYPES),
@ -64,6 +71,13 @@ defaults_flag_in_config = {
**DEFAULTS_FLAG_IN_CONFIG_CRYTIC_COMPILE,
}
deprecated_flags = {
"fail_pedantic": True,
"fail_low": False,
"fail_medium": False,
"fail_high": False,
}
def read_config_file(args: argparse.Namespace) -> None:
# No config file was provided as an argument
@ -80,6 +94,12 @@ def read_config_file(args: argparse.Namespace) -> None:
with open(args.config_file, encoding="utf8") as f:
config = json.load(f)
for key, elem in config.items():
if key in deprecated_flags:
logger.info(
yellow(f"{args.config_file} has a deprecated key: {key} : {elem}")
)
migrate_config_options(args, key, elem)
continue
if key not in defaults_flag_in_config:
logger.info(
yellow(f"{args.config_file} has an unknown key: {key} : {elem}")
@ -94,6 +114,28 @@ def read_config_file(args: argparse.Namespace) -> None:
logger.error(yellow("Falling back to the default settings..."))
def migrate_config_options(args: argparse.Namespace, key: str, elem):
if key.startswith("fail_") and getattr(args, "fail_on") == defaults_flag_in_config["fail_on"]:
if key == "fail_pedantic":
pedantic_setting = elem
fail_on = FailOnLevel.PEDANTIC if pedantic_setting else FailOnLevel.NONE
setattr(args, "fail_on", fail_on)
logger.info(f"Migrating fail_pedantic: {pedantic_setting} as fail_on: {fail_on.value}")
elif key == "fail_low" and elem is True:
logger.info("Migrating fail_low: true -> fail_on: low")
setattr(args, "fail_on", FailOnLevel.LOW)
elif key == "fail_medium" and elem is True:
logger.info("Migrating fail_medium: true -> fail_on: medium")
setattr(args, "fail_on", FailOnLevel.MEDIUM)
elif key == "fail_high" and elem is True:
logger.info("Migrating fail_high: true -> fail_on: high")
setattr(args, "fail_on", FailOnLevel.HIGH)
else:
logger.warning(yellow(f"Key {key} was deprecated but no migration was provided"))
def output_to_markdown(
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],

@ -4,7 +4,9 @@ from typing import Union
from slither.exceptions import SlitherError
def convert_string_to_fraction(val: Union[str, int]) -> Fraction:
def convert_string_to_fraction(val: Union[str, bytes, int]) -> Fraction:
if isinstance(val, bytes):
return int.from_bytes(val, byteorder="big")
if isinstance(val, int):
return Fraction(val)
if val.startswith(("0x", "0X")):

@ -0,0 +1,105 @@
from dataclasses import dataclass, field
from pathlib import Path
from typing import List, Tuple
from slither import Slither
from slither.utils.myprettytable import MyPrettyTable
from slither.utils.tests_pattern import is_test_file
@dataclass
class LoCInfo:
loc: int = 0
sloc: int = 0
cloc: int = 0
def total(self) -> int:
return self.loc + self.sloc + self.cloc
@dataclass
class LoC:
src: LoCInfo = field(default_factory=LoCInfo)
dep: LoCInfo = field(default_factory=LoCInfo)
test: LoCInfo = field(default_factory=LoCInfo)
def to_pretty_table(self) -> MyPrettyTable:
table = MyPrettyTable(["", "src", "dep", "test"])
table.add_row(["loc", str(self.src.loc), str(self.dep.loc), str(self.test.loc)])
table.add_row(["sloc", str(self.src.sloc), str(self.dep.sloc), str(self.test.sloc)])
table.add_row(["cloc", str(self.src.cloc), str(self.dep.cloc), str(self.test.cloc)])
table.add_row(
["Total", str(self.src.total()), str(self.dep.total()), str(self.test.total())]
)
return table
def count_lines(contract_lines: List[str]) -> Tuple[int, int, int]:
"""Function to count and classify the lines of code in a contract.
Args:
contract_lines: list(str) representing the lines of a contract.
Returns:
tuple(int, int, int) representing (cloc, sloc, loc)
"""
multiline_comment = False
cloc = 0
sloc = 0
loc = 0
for line in contract_lines:
loc += 1
stripped_line = line.strip()
if not multiline_comment:
if stripped_line.startswith("//"):
cloc += 1
elif "/*" in stripped_line:
# Account for case where /* is followed by */ on the same line.
# If it is, then multiline_comment does not need to be set to True
start_idx = stripped_line.find("/*")
end_idx = stripped_line.find("*/", start_idx + 2)
if end_idx == -1:
multiline_comment = True
cloc += 1
elif stripped_line:
sloc += 1
else:
cloc += 1
if "*/" in stripped_line:
multiline_comment = False
return cloc, sloc, loc
def _update_lines(loc_info: LoCInfo, lines: list) -> None:
"""An internal function used to update (mutate in place) the loc_info.
Args:
loc_info: LoCInfo to be updated
lines: list(str) representing the lines of a contract.
"""
cloc, sloc, loc = count_lines(lines)
loc_info.loc += loc
loc_info.cloc += cloc
loc_info.sloc += sloc
def compute_loc_metrics(slither: Slither) -> LoC:
"""Used to compute the lines of code metrics for a Slither object.
Args:
slither: A Slither object
Returns:
A LoC object
"""
loc = LoC()
for filename, source_code in slither.source_code.items():
current_lines = source_code.splitlines()
is_dep = False
if slither.crytic_compile:
is_dep = slither.crytic_compile.is_dependency(filename)
loc_type = loc.dep if is_dep else loc.test if is_test_file(Path(filename)) else loc.src
_update_lines(loc_type, current_lines)
return loc

@ -19,10 +19,12 @@ from slither.core.variables.local_variable_init_from_tuple import LocalVariableI
from slither.core.variables.state_variable import StateVariable
from slither.analyses.data_dependency.data_dependency import get_dependencies
from slither.core.variables.variable import Variable
from slither.core.expressions.literal import Literal
from slither.core.expressions.identifier import Identifier
from slither.core.expressions.call_expression import CallExpression
from slither.core.expressions.assignment_operation import AssignmentOperation
from slither.core.expressions import (
Literal,
Identifier,
CallExpression,
AssignmentOperation,
)
from slither.core.cfg.node import Node, NodeType
from slither.slithir.operations import (
Operation,
@ -61,11 +63,42 @@ from slither.slithir.variables import (
from slither.tools.read_storage.read_storage import SlotInfo, SlitherReadStorage
class TaintedExternalContract:
def __init__(self, contract: "Contract") -> None:
self._contract: Contract = contract
self._tainted_functions: List[Function] = []
self._tainted_variables: List[Variable] = []
@property
def contract(self) -> Contract:
return self._contract
@property
def tainted_functions(self) -> List[Function]:
return self._tainted_functions
def add_tainted_function(self, f: Function):
self._tainted_functions.append(f)
@property
def tainted_variables(self) -> List[Variable]:
return self._tainted_variables
def add_tainted_variable(self, v: Variable):
self._tainted_variables.append(v)
# pylint: disable=too-many-locals
def compare(
v1: Contract, v2: Contract
v1: Contract, v2: Contract, include_external: bool = False
) -> Tuple[
List[Variable], List[Variable], List[Variable], List[Function], List[Function], List[Function]
List[Variable],
List[Variable],
List[Variable],
List[Function],
List[Function],
List[Function],
List[TaintedExternalContract],
]:
"""
Compares two versions of a contract. Most useful for upgradeable (logic) contracts,
@ -74,6 +107,7 @@ def compare(
Args:
v1: Original version of (upgradeable) contract
v2: Updated version of (upgradeable) contract
include_external: Optional flag to enable cross-contract external taint analysis
Returns:
missing-vars-in-v2: list[Variable],
@ -82,6 +116,7 @@ def compare(
new-functions: list[Function],
modified-functions: list[Function],
tainted-functions: list[Function]
tainted-contracts: list[TaintedExternalContract]
"""
order_vars1 = [
@ -113,17 +148,13 @@ def compare(
if sig not in func_sigs1:
new_modified_functions.append(function)
new_functions.append(function)
new_modified_function_vars += (
function.state_variables_read + function.state_variables_written
)
new_modified_function_vars += function.all_state_variables_written()
elif not function.is_constructor_variables and is_function_modified(
orig_function, function
):
new_modified_functions.append(function)
modified_functions.append(function)
new_modified_function_vars += (
function.state_variables_read + function.state_variables_written
)
new_modified_function_vars += function.all_state_variables_written()
# Find all unmodified functions that call a modified function or read/write the
# same state variable(s) as a new/modified function, i.e., tainted functions
@ -140,25 +171,28 @@ def compare(
tainted_vars = [
var
for var in set(new_modified_function_vars)
if var in function.variables_read_or_written
if var in function.all_state_variables_read() + function.all_state_variables_written()
and not var.is_constant
and not var.is_immutable
]
if len(modified_calls) > 0 or len(tainted_vars) > 0:
tainted_functions.append(function)
# Find all new or tainted variables, i.e., variables that are read or written by a new/modified/tainted function
# Find all new or tainted variables, i.e., variables that are written by a new/modified/tainted function
for var in order_vars2:
read_by = v2.get_functions_reading_from_variable(var)
written_by = v2.get_functions_writing_to_variable(var)
if v1.get_state_variable_from_name(var.name) is None:
if next((v for v in v1.state_variables_ordered if v.name == var.name), None) is None:
new_variables.append(var)
elif any(
func in read_by or func in written_by
for func in new_modified_functions + tainted_functions
):
elif any(func in written_by for func in new_modified_functions + tainted_functions):
tainted_variables.append(var)
tainted_contracts = []
if include_external:
# Find all external contracts and functions called by new/modified/tainted functions
tainted_contracts = tainted_external_contracts(
new_functions + modified_functions + tainted_functions
)
return (
missing_vars_in_v2,
new_variables,
@ -166,9 +200,137 @@ def compare(
new_functions,
modified_functions,
tainted_functions,
tainted_contracts,
)
def tainted_external_contracts(funcs: List[Function]) -> List[TaintedExternalContract]:
"""
Takes a list of functions from one contract, finds any calls in these to functions in external contracts,
and determines which variables and functions in the external contracts are tainted by these external calls.
Args:
funcs: a list of Function objects to search for external calls.
Returns:
TaintedExternalContract() (
contract: Contract,
tainted_functions: List[TaintedFunction],
tainted_variables: List[TaintedVariable]
)
"""
tainted_contracts: dict[str, TaintedExternalContract] = {}
tainted_list: list[TaintedExternalContract] = []
for func in funcs:
for contract, target in func.all_high_level_calls():
if contract.is_library:
# Not interested in library calls
continue
if contract.name not in tainted_contracts:
# A contract may be tainted by multiple function calls - only make one TaintedExternalContract object
tainted_contracts[contract.name] = TaintedExternalContract(contract)
if (
isinstance(target, Function)
and target not in funcs
and target not in (f for f in tainted_contracts[contract.name].tainted_functions)
and not (target.is_constructor or target.is_fallback or target.is_receive)
):
# Found a high-level call to a new tainted function
tainted_contracts[contract.name].add_tainted_function(target)
for var in target.all_state_variables_written():
# Consider as tainted all variables written by the tainted function
if var not in (v for v in tainted_contracts[contract.name].tainted_variables):
tainted_contracts[contract.name].add_tainted_variable(var)
elif (
isinstance(target, StateVariable)
and target not in (v for v in tainted_contracts[contract.name].tainted_variables)
and not (target.is_constant or target.is_immutable)
):
# Found a new high-level call to a public state variable getter
tainted_contracts[contract.name].add_tainted_variable(target)
for c in tainted_contracts.values():
tainted_list.append(c)
contract = c.contract
variables = c.tainted_variables
for var in variables:
# For each tainted variable, consider as tainted any function that reads or writes to it
read_write = set(
contract.get_functions_reading_from_variable(var)
+ contract.get_functions_writing_to_variable(var)
)
for f in read_write:
if f not in tainted_contracts[contract.name].tainted_functions and not (
f.is_constructor or f.is_fallback or f.is_receive
):
c.add_tainted_function(f)
return tainted_list
def tainted_inheriting_contracts(
tainted_contracts: List[TaintedExternalContract], contracts: List[Contract] = None
) -> List[TaintedExternalContract]:
"""
Takes a list of TaintedExternalContract obtained from tainted_external_contracts, and finds any contracts which
inherit a tainted contract, as well as any functions that call tainted functions or read tainted variables in
the inherited contract.
Args:
tainted_contracts: the list obtained from `tainted_external_contracts` or `compare`.
contracts: (optional) the list of contracts to check for inheritance. If not provided, defaults to
`contract.compilation_unit.contracts` for each contract in tainted_contracts.
Returns:
An updated list of TaintedExternalContract, including all from the input list.
"""
for tainted in tainted_contracts:
contract = tainted.contract
check_contracts = contracts
if contracts is None:
check_contracts = contract.compilation_unit.contracts
# We are only interested in checking contracts that inherit a tainted contract
check_contracts = [
c
for c in check_contracts
if c.name not in [t.contract.name for t in tainted_contracts]
and contract.name in [i.name for i in c.inheritance]
]
for c in check_contracts:
new_taint = TaintedExternalContract(c)
for f in c.functions_declared:
# Search for functions that call an inherited tainted function or access an inherited tainted variable
internal_calls = [c for c in f.all_internal_calls() if isinstance(c, Function)]
if any(
call.canonical_name == t.canonical_name
for t in tainted.tainted_functions
for call in internal_calls
) or any(
var.canonical_name == t.canonical_name
for t in tainted.tainted_variables
for var in f.all_state_variables_read() + f.all_state_variables_written()
):
new_taint.add_tainted_function(f)
for f in new_taint.tainted_functions:
# For each newly found tainted function, consider as tainted any variable it writes to
for var in f.all_state_variables_written():
if var not in (
v for v in tainted.tainted_variables + new_taint.tainted_variables
):
new_taint.add_tainted_variable(var)
for var in new_taint.tainted_variables:
# For each newly found tainted variable, consider as tainted any function that reads or writes to it
read_write = set(
contract.get_functions_reading_from_variable(var)
+ contract.get_functions_writing_to_variable(var)
)
for f in read_write:
if f not in (
t for t in tainted.tainted_functions + new_taint.tainted_functions
) and not (f.is_constructor or f.is_fallback or f.is_receive):
new_taint.add_tainted_function(f)
if len(new_taint.tainted_functions) > 0:
tainted_contracts.append(new_taint)
return tainted_contracts
def get_missing_vars(v1: Contract, v2: Contract) -> List[StateVariable]:
"""
Gets all non-constant/immutable StateVariables that appear in v1 but not v2
@ -220,6 +382,8 @@ def is_function_modified(f1: Function, f2: Function) -> bool:
visited.extend([node_f1, node_f2])
queue_f1.extend(son for son in node_f1.sons if son not in visited)
queue_f2.extend(son for son in node_f2.sons if son not in visited)
if len(node_f1.irs) != len(node_f2.irs):
return True
for i, ir in enumerate(node_f1.irs):
if encode_ir_for_compare(ir) != encode_ir_for_compare(node_f2.irs[i]):
return True
@ -273,13 +437,13 @@ def encode_ir_for_compare(ir: Operation) -> str:
if isinstance(ir, Assignment):
return f"({encode_var_for_compare(ir.lvalue)}):=({encode_var_for_compare(ir.rvalue)})"
if isinstance(ir, Index):
return f"index({ntype(ir.index_type)})"
return f"index({ntype(ir.variable_right.type)})"
if isinstance(ir, Member):
return "member" # .format(ntype(ir._type))
if isinstance(ir, Length):
return "length"
if isinstance(ir, Binary):
return f"binary({str(ir.variable_left)}{str(ir.type)}{str(ir.variable_right)})"
return f"binary({encode_var_for_compare(ir.variable_left)}{ir.type}{encode_var_for_compare(ir.variable_right)})"
if isinstance(ir, Unary):
return f"unary({str(ir.type)})"
if isinstance(ir, Condition):
@ -387,7 +551,7 @@ def encode_var_for_compare(var: Variable) -> str:
# variables
if isinstance(var, Constant):
return f"constant({ntype(var.type)})"
return f"constant({ntype(var.type)},{var.value})"
if isinstance(var, SolidityVariableComposed):
return f"solidity_variable_composed({var.name})"
if isinstance(var, SolidityVariable):
@ -397,9 +561,16 @@ def encode_var_for_compare(var: Variable) -> str:
if isinstance(var, ReferenceVariable):
return f"reference({ntype(var.type)})"
if isinstance(var, LocalVariable):
return f"local_solc_variable({var.location})"
return f"local_solc_variable({ntype(var.type)},{var.location})"
if isinstance(var, StateVariable):
return f"state_solc_variable({ntype(var.type)})"
if not (var.is_constant or var.is_immutable):
try:
slot, _ = var.contract.compilation_unit.storage_layout_of(var.contract, var)
except KeyError:
slot = var.name
else:
slot = var.name
return f"state_solc_variable({ntype(var.type)},{slot})"
if isinstance(var, LocalVariableInitFromTuple):
return "local_variable_init_tuple"
if isinstance(var, TupleVariable):
@ -455,6 +626,7 @@ def get_proxy_implementation_var(proxy: Contract) -> Optional[Variable]:
try:
delegate = next(var for var in dependencies if isinstance(var, StateVariable))
except StopIteration:
# TODO: Handle cases where get_dependencies doesn't return any state variables.
return delegate
return delegate

@ -1,5 +1,6 @@
from fractions import Fraction
from typing import Union
from Crypto.Hash import keccak
from slither.core import expressions
from slither.core.expressions import (
@ -11,9 +12,9 @@ from slither.core.expressions import (
UnaryOperation,
TupleExpression,
TypeConversion,
CallExpression,
)
from slither.core.variables import Variable
from slither.utils.integer_conversion import convert_string_to_fraction, convert_string_to_int
from slither.visitors.expression.expression import ExpressionVisitor
from slither.core.solidity_types.elementary_type import ElementaryType
@ -65,23 +66,31 @@ class ConstantFolding(ExpressionVisitor):
value = value & (2**256 - 1)
return Literal(value, self._type)
# pylint: disable=import-outside-toplevel
def _post_identifier(self, expression: Identifier) -> None:
if not isinstance(expression.value, Variable):
return
if not expression.value.is_constant:
from slither.core.declarations.solidity_variables import SolidityFunction
if isinstance(expression.value, Variable):
if expression.value.is_constant:
expr = expression.value.expression
# assumption that we won't have infinite loop
# Everything outside of literal
if isinstance(
expr,
(BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion),
):
cf = ConstantFolding(expr, self._type)
expr = cf.result()
assert isinstance(expr, Literal)
set_val(expression, convert_string_to_int(expr.converted_value))
else:
raise NotConstant
elif isinstance(expression.value, SolidityFunction):
set_val(expression, expression.value)
else:
raise NotConstant
expr = expression.value.expression
# assumption that we won't have infinite loop
# Everything outside of literal
if isinstance(
expr, (BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion)
):
cf = ConstantFolding(expr, self._type)
expr = cf.result()
assert isinstance(expr, Literal)
set_val(expression, convert_string_to_int(expr.converted_value))
# pylint: disable=too-many-branches
# pylint: disable=too-many-branches,too-many-statements
def _post_binary_operation(self, expression: BinaryOperation) -> None:
expression_left = expression.expression_left
expression_right = expression.expression_right
@ -95,7 +104,6 @@ class ConstantFolding(ExpressionVisitor):
(Literal, BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion),
):
raise NotConstant
left = get_val(expression_left)
right = get_val(expression_right)
@ -183,7 +191,9 @@ class ConstantFolding(ExpressionVisitor):
raise NotConstant
def _post_literal(self, expression: Literal) -> None:
if expression.converted_value in ["true", "false"]:
if str(expression.type) == "bool":
set_val(expression, expression.converted_value)
elif str(expression.type) == "string":
set_val(expression, expression.converted_value)
else:
try:
@ -195,7 +205,14 @@ class ConstantFolding(ExpressionVisitor):
raise NotConstant
def _post_call_expression(self, expression: expressions.CallExpression) -> None:
raise NotConstant
called = get_val(expression.called)
args = [get_val(arg) for arg in expression.arguments]
if called.name == "keccak256(bytes)":
digest = keccak.new(digest_bits=256)
digest.update(str(args[0]).encode("utf-8"))
set_val(expression, digest.digest())
else:
raise NotConstant
def _post_conditional_expression(self, expression: expressions.ConditionalExpression) -> None:
raise NotConstant
@ -247,10 +264,24 @@ class ConstantFolding(ExpressionVisitor):
expr = expression.expression
if not isinstance(
expr,
(Literal, BinaryOperation, UnaryOperation, Identifier, TupleExpression, TypeConversion),
(
Literal,
BinaryOperation,
UnaryOperation,
Identifier,
TupleExpression,
TypeConversion,
CallExpression,
),
):
raise NotConstant
cf = ConstantFolding(expr, self._type)
expr = cf.result()
assert isinstance(expr, Literal)
set_val(expression, convert_string_to_fraction(expr.converted_value))
if str(expression.type).startswith("uint") and isinstance(expr.value, bytes):
value = int.from_bytes(expr.value, "big")
elif str(expression.type).startswith("byte") and isinstance(expr.value, int):
value = int.to_bytes(expr.value, 32, "big")
else:
value = convert_string_to_fraction(expr.converted_value)
set_val(expression, value)

@ -1,12 +1,12 @@
# pylint: disable=redefined-outer-name
import os
from pathlib import Path
import tempfile
import shutil
import tempfile
from pathlib import Path
from contextlib import contextmanager
import pytest
from filelock import FileLock
from solc_select import solc_select
import pytest
from slither import Slither

@ -1,12 +1,14 @@
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#39) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23) which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#11) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28) which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#2) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28) which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#2) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23) which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#39) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28) which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#11) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23) which only takes arrays by value
E.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#57-61) passes array E.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#54) by reference to E.setByValue(uint256[1],uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#63-66) which only takes arrays by value

@ -1,12 +1,14 @@
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#39) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23) which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#11) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28) which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#2) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28) which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#2) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23) which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#39) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28) which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#11) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23) which only takes arrays by value
E.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#57-61) passes array E.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#54) by reference to E.setByValue(uint256[1],uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#63-66) which only takes arrays by value

@ -1,12 +1,14 @@
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#39) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23) which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#11) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28) which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#2) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28) which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#2) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23) which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#39) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28) which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#11) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23) which only takes arrays by value
E.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#57-61) passes array E.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#54) by reference to E.setByValue(uint256[1],uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#63-66) which only takes arrays by value

@ -1,12 +1,14 @@
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#39) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23) which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#11) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28) which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#2) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28) which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#2) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23) which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#39) by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28) which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#11) by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23) which only takes arrays by value
E.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#57-61) passes array E.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#54) by reference to E.setByValue(uint256[1],uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#63-66) which only takes arrays by value

@ -12,12 +12,12 @@ FurtherExtendedContract.this (tests/e2e/detectors/test_data/shadowing-builtin/0.
BaseContract.now (tests/e2e/detectors/test_data/shadowing-builtin/0.4.25/shadowing_builtin_symbols.sol#5) (state variable) shadows built-in symbol"
BaseContractrevert(bool) (tests/e2e/detectors/test_data/shadowing-builtin/0.4.25/shadowing_builtin_symbols.sol#7) (event) shadows built-in symbol"
ExtendedContract.assert(bool).msg (tests/e2e/detectors/test_data/shadowing-builtin/0.4.25/shadowing_builtin_symbols.sol#14) (local variable) shadows built-in symbol"
ExtendedContract.assert(bool) (tests/e2e/detectors/test_data/shadowing-builtin/0.4.25/shadowing_builtin_symbols.sol#13-15) (function) shadows built-in symbol"
BaseContract.revert(bool) (tests/e2e/detectors/test_data/shadowing-builtin/0.4.25/shadowing_builtin_symbols.sol#7) (event) shadows built-in symbol"
FurtherExtendedContract.require().sha3 (tests/e2e/detectors/test_data/shadowing-builtin/0.4.25/shadowing_builtin_symbols.sol#26) (local variable) shadows built-in symbol"
FurtherExtendedContract.blockhash (tests/e2e/detectors/test_data/shadowing-builtin/0.4.25/shadowing_builtin_symbols.sol#19) (state variable) shadows built-in symbol"

@ -10,12 +10,12 @@ FurtherExtendedContract.this (tests/e2e/detectors/test_data/shadowing-builtin/0.
BaseContract.now (tests/e2e/detectors/test_data/shadowing-builtin/0.5.16/shadowing_builtin_symbols.sol#5) (state variable) shadows built-in symbol"
BaseContractrevert(bool) (tests/e2e/detectors/test_data/shadowing-builtin/0.5.16/shadowing_builtin_symbols.sol#7) (event) shadows built-in symbol"
ExtendedContract.assert(bool).msg (tests/e2e/detectors/test_data/shadowing-builtin/0.5.16/shadowing_builtin_symbols.sol#14) (local variable) shadows built-in symbol"
ExtendedContract.assert(bool) (tests/e2e/detectors/test_data/shadowing-builtin/0.5.16/shadowing_builtin_symbols.sol#13-15) (function) shadows built-in symbol"
BaseContract.revert(bool) (tests/e2e/detectors/test_data/shadowing-builtin/0.5.16/shadowing_builtin_symbols.sol#7) (event) shadows built-in symbol"
FurtherExtendedContract.require().sha3 (tests/e2e/detectors/test_data/shadowing-builtin/0.5.16/shadowing_builtin_symbols.sol#26) (local variable) shadows built-in symbol"
FurtherExtendedContract.blockhash (tests/e2e/detectors/test_data/shadowing-builtin/0.5.16/shadowing_builtin_symbols.sol#19) (state variable) shadows built-in symbol"

@ -0,0 +1,18 @@
Loop condition at i < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#36) should use cached array length instead of referencing `length` member of the storage array.
Loop condition at i_scope_22 < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#166) should use cached array length instead of referencing `length` member of the storage array.
Loop condition at j_scope_11 < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#108) should use cached array length instead of referencing `length` member of the storage array.
Loop condition at i_scope_6 < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#79) should use cached array length instead of referencing `length` member of the storage array.
Loop condition at i_scope_21 < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#160) should use cached array length instead of referencing `length` member of the storage array.
Loop condition at k_scope_9 < array2.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#98) should use cached array length instead of referencing `length` member of the storage array.
Loop condition at k_scope_17 < array2.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#132) should use cached array length instead of referencing `length` member of the storage array.
Loop condition at j_scope_15 < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#125) should use cached array length instead of referencing `length` member of the storage array.
Loop condition at i_scope_4 < array.length (tests/e2e/detectors/test_data/cache-array-length/0.8.17/CacheArrayLength.sol#67) should use cached array length instead of referencing `length` member of the storage array.

@ -10,7 +10,7 @@ FurtherExtendedContract.shadowingParent(uint256).y (tests/e2e/detectors/test_dat
- BaseContract.y (tests/e2e/detectors/test_data/shadowing-local/0.4.25/shadowing_local_variable.sol#5) (state variable)
FurtherExtendedContract.shadowingParent(uint256).v (tests/e2e/detectors/test_data/shadowing-local/0.4.25/shadowing_local_variable.sol#25) shadows:
- ExtendedContractv() (tests/e2e/detectors/test_data/shadowing-local/0.4.25/shadowing_local_variable.sol#13) (event)
- ExtendedContract.v() (tests/e2e/detectors/test_data/shadowing-local/0.4.25/shadowing_local_variable.sol#13) (event)
FurtherExtendedContract.shadowingParent(uint256).w (tests/e2e/detectors/test_data/shadowing-local/0.4.25/shadowing_local_variable.sol#25) shadows:
- FurtherExtendedContract.w() (tests/e2e/detectors/test_data/shadowing-local/0.4.25/shadowing_local_variable.sol#20-23) (modifier)

@ -10,7 +10,7 @@ FurtherExtendedContract.shadowingParent(uint256).y (tests/e2e/detectors/test_dat
- BaseContract.y (tests/e2e/detectors/test_data/shadowing-local/0.5.16/shadowing_local_variable.sol#5) (state variable)
FurtherExtendedContract.shadowingParent(uint256).v (tests/e2e/detectors/test_data/shadowing-local/0.5.16/shadowing_local_variable.sol#25) shadows:
- ExtendedContractv() (tests/e2e/detectors/test_data/shadowing-local/0.5.16/shadowing_local_variable.sol#13) (event)
- ExtendedContract.v() (tests/e2e/detectors/test_data/shadowing-local/0.5.16/shadowing_local_variable.sol#13) (event)
FurtherExtendedContract.shadowingParent(uint256).w (tests/e2e/detectors/test_data/shadowing-local/0.5.16/shadowing_local_variable.sol#25) shadows:
- FurtherExtendedContract.w() (tests/e2e/detectors/test_data/shadowing-local/0.5.16/shadowing_local_variable.sol#20-23) (modifier)

@ -5,7 +5,7 @@ FurtherExtendedContract.shadowingParent(uint256).y (tests/e2e/detectors/test_dat
- BaseContract.y (tests/e2e/detectors/test_data/shadowing-local/0.6.11/shadowing_local_variable.sol#5) (state variable)
FurtherExtendedContract.shadowingParent(uint256).v (tests/e2e/detectors/test_data/shadowing-local/0.6.11/shadowing_local_variable.sol#25) shadows:
- ExtendedContractv() (tests/e2e/detectors/test_data/shadowing-local/0.6.11/shadowing_local_variable.sol#13) (event)
- ExtendedContract.v() (tests/e2e/detectors/test_data/shadowing-local/0.6.11/shadowing_local_variable.sol#13) (event)
FurtherExtendedContract.shadowingParent(uint256).w (tests/e2e/detectors/test_data/shadowing-local/0.6.11/shadowing_local_variable.sol#25) shadows:
- FurtherExtendedContract.w() (tests/e2e/detectors/test_data/shadowing-local/0.6.11/shadowing_local_variable.sol#20-23) (modifier)

@ -5,7 +5,7 @@ FurtherExtendedContract.shadowingParent(uint256).y (tests/e2e/detectors/test_dat
- BaseContract.y (tests/e2e/detectors/test_data/shadowing-local/0.7.6/shadowing_local_variable.sol#5) (state variable)
FurtherExtendedContract.shadowingParent(uint256).v (tests/e2e/detectors/test_data/shadowing-local/0.7.6/shadowing_local_variable.sol#25) shadows:
- ExtendedContractv() (tests/e2e/detectors/test_data/shadowing-local/0.7.6/shadowing_local_variable.sol#13) (event)
- ExtendedContract.v() (tests/e2e/detectors/test_data/shadowing-local/0.7.6/shadowing_local_variable.sol#13) (event)
FurtherExtendedContract.shadowingParent(uint256).w (tests/e2e/detectors/test_data/shadowing-local/0.7.6/shadowing_local_variable.sol#25) shadows:
- FurtherExtendedContract.w() (tests/e2e/detectors/test_data/shadowing-local/0.7.6/shadowing_local_variable.sol#20-23) (modifier)

@ -18,10 +18,10 @@ Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-co
Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#56) is not in mixedCase
Event namingevent_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#23) is not in CapWords
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#68) is single letter l, O, or I, which should not be used
Event naming.event_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#23) is not in CapWords
Modifier naming.CantDo() (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#41-43) is not in mixedCase
Function naming.GetOne() (tests/e2e/detectors/test_data/naming-convention/0.4.25/naming_convention.sol#30-33) is not in mixedCase

@ -18,10 +18,10 @@ Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-co
Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#56) is not in mixedCase
Event namingevent_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#23) is not in CapWords
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#68) is single letter l, O, or I, which should not be used
Event naming.event_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#23) is not in CapWords
Modifier naming.CantDo() (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#41-43) is not in mixedCase
Function naming.GetOne() (tests/e2e/detectors/test_data/naming-convention/0.5.16/naming_convention.sol#30-33) is not in mixedCase

@ -18,10 +18,10 @@ Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-co
Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#56) is not in mixedCase
Event namingevent_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#23) is not in CapWords
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#68) is single letter l, O, or I, which should not be used
Event naming.event_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#23) is not in CapWords
Modifier naming.CantDo() (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#41-43) is not in mixedCase
Function naming.GetOne() (tests/e2e/detectors/test_data/naming-convention/0.6.11/naming_convention.sol#30-33) is not in mixedCase

@ -18,10 +18,10 @@ Parameter T.test(uint256,uint256)._used (tests/e2e/detectors/test_data/naming-co
Variable T._myPublicVar (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#56) is not in mixedCase
Event namingevent_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#23) is not in CapWords
Variable T.O (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#68) is single letter l, O, or I, which should not be used
Event naming.event_(uint256) (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#23) is not in CapWords
Modifier naming.CantDo() (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#41-43) is not in mixedCase
Function naming.GetOne() (tests/e2e/detectors/test_data/naming-convention/0.7.6/naming_convention.sol#30-33) is not in mixedCase

@ -1,8 +1,8 @@
ERC20 event IERC20BadTransfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.4.25/erc20_indexed.sol#19)does not index parameter to
ERC20 event IERC20Bad.Approval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.4.25/erc20_indexed.sol#20)does not index parameter owner
ERC20 event IERC20BadApproval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.4.25/erc20_indexed.sol#20)does not index parameter owner
ERC20 event IERC20Bad.Transfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.4.25/erc20_indexed.sol#19)does not index parameter from
ERC20 event IERC20BadTransfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.4.25/erc20_indexed.sol#19)does not index parameter from
ERC20 event IERC20Bad.Approval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.4.25/erc20_indexed.sol#20)does not index parameter spender
ERC20 event IERC20BadApproval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.4.25/erc20_indexed.sol#20)does not index parameter spender
ERC20 event IERC20Bad.Transfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.4.25/erc20_indexed.sol#19)does not index parameter to

@ -1,8 +1,8 @@
ERC20 event IERC20BadTransfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.5.16/erc20_indexed.sol#19)does not index parameter to
ERC20 event IERC20Bad.Approval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.5.16/erc20_indexed.sol#20)does not index parameter owner
ERC20 event IERC20BadApproval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.5.16/erc20_indexed.sol#20)does not index parameter owner
ERC20 event IERC20Bad.Transfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.5.16/erc20_indexed.sol#19)does not index parameter from
ERC20 event IERC20BadTransfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.5.16/erc20_indexed.sol#19)does not index parameter from
ERC20 event IERC20Bad.Approval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.5.16/erc20_indexed.sol#20)does not index parameter spender
ERC20 event IERC20BadApproval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.5.16/erc20_indexed.sol#20)does not index parameter spender
ERC20 event IERC20Bad.Transfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.5.16/erc20_indexed.sol#19)does not index parameter to

@ -1,8 +1,8 @@
ERC20 event IERC20BadTransfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.6.11/erc20_indexed.sol#19)does not index parameter to
ERC20 event IERC20Bad.Approval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.6.11/erc20_indexed.sol#20)does not index parameter owner
ERC20 event IERC20BadApproval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.6.11/erc20_indexed.sol#20)does not index parameter owner
ERC20 event IERC20Bad.Transfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.6.11/erc20_indexed.sol#19)does not index parameter from
ERC20 event IERC20BadTransfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.6.11/erc20_indexed.sol#19)does not index parameter from
ERC20 event IERC20Bad.Approval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.6.11/erc20_indexed.sol#20)does not index parameter spender
ERC20 event IERC20BadApproval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.6.11/erc20_indexed.sol#20)does not index parameter spender
ERC20 event IERC20Bad.Transfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.6.11/erc20_indexed.sol#19)does not index parameter to

@ -1,8 +1,8 @@
ERC20 event IERC20BadTransfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.7.6/erc20_indexed.sol#19)does not index parameter to
ERC20 event IERC20Bad.Approval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.7.6/erc20_indexed.sol#20)does not index parameter owner
ERC20 event IERC20BadApproval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.7.6/erc20_indexed.sol#20)does not index parameter owner
ERC20 event IERC20Bad.Transfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.7.6/erc20_indexed.sol#19)does not index parameter from
ERC20 event IERC20BadTransfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.7.6/erc20_indexed.sol#19)does not index parameter from
ERC20 event IERC20Bad.Approval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.7.6/erc20_indexed.sol#20)does not index parameter spender
ERC20 event IERC20BadApproval(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.7.6/erc20_indexed.sol#20)does not index parameter spender
ERC20 event IERC20Bad.Transfer(address,address,uint256) (tests/e2e/detectors/test_data/erc20-indexed/0.7.6/erc20_indexed.sol#19)does not index parameter to

@ -48,4 +48,25 @@ contract D {
}
}
contract E {
uint[1] public x; // storage
uint[1] public y; // storage
function f() public {
uint[1] memory temp;
setByValue(temp, x); // can set temp, but cannot set x
setByRef(temp, y); // can set temp and y
}
function setByValue(uint[1] memory arr, uint[1] memory arr2) internal {
arr[0] = 1;
arr2[0] = 2;
}
function setByRef(uint[1] memory arr, uint[1] storage arr2) internal {
arr[0] = 2;
arr2[0] = 3;
}
}

@ -48,4 +48,25 @@ contract D {
}
}
contract E {
uint[1] public x; // storage
uint[1] public y; // storage
function f() public {
uint[1] memory temp;
setByValue(temp, x); // can set temp, but cannot set x
setByRef(temp, y); // can set temp and y
}
function setByValue(uint[1] memory arr, uint[1] memory arr2) internal {
arr[0] = 1;
arr2[0] = 2;
}
function setByRef(uint[1] memory arr, uint[1] storage arr2) internal {
arr[0] = 2;
arr2[0] = 3;
}
}

@ -48,4 +48,25 @@ contract D {
}
}
contract E {
uint[1] public x; // storage
uint[1] public y; // storage
function f() public {
uint[1] memory temp;
setByValue(temp, x); // can set temp, but cannot set x
setByRef(temp, y); // can set temp and y
}
function setByValue(uint[1] memory arr, uint[1] memory arr2) internal {
arr[0] = 1;
arr2[0] = 2;
}
function setByRef(uint[1] memory arr, uint[1] storage arr2) internal {
arr[0] = 2;
arr2[0] = 3;
}
}

@ -48,4 +48,25 @@ contract D {
}
}
contract E {
uint[1] public x; // storage
uint[1] public y; // storage
function f() public {
uint[1] memory temp;
setByValue(temp, x); // can set temp, but cannot set x
setByRef(temp, y); // can set temp and y
}
function setByValue(uint[1] memory arr, uint[1] memory arr2) internal {
arr[0] = 1;
arr2[0] = 2;
}
function setByRef(uint[1] memory arr, uint[1] storage arr2) internal {
arr[0] = 2;
arr2[0] = 3;
}
}

@ -0,0 +1,171 @@
pragma solidity 0.8.17;
contract CacheArrayLength
{
struct S
{
uint s;
}
S[] array;
S[] array2;
function h() external
{
}
function g() internal
{
this.h();
}
function h_view() external view
{
}
function g_view() internal view
{
this.h_view();
}
function f() public
{
// array accessed but length doesn't change
for (uint i = 0; i < array.length; i++) // warning should appear
{
array[i] = S(0);
}
// array.length doesn't change, but array.length not used in loop condition
for (uint i = array.length; i >= 0; i--)
{
}
// array.length changes in the inner loop
for (uint i = 0; i < array.length; i++)
{
for (uint j = i; j < 2 * i; j++)
array.push(S(j));
}
// array.length changes
for (uint i = 0; i < array.length; i++)
{
array.pop();
}
// array.length changes
for (uint i = 0; i < array.length; i++)
{
delete array;
}
// array.length doesn't change despite using delete
for (uint i = 0; i < array.length; i++) // warning should appear
{
delete array[i];
}
// array.length changes; push used in more complex expression
for (uint i = 0; i < array.length; i++)
{
array.push() = S(i);
}
// array.length doesn't change
for (uint i = 0; i < array.length; i++) // warning should appear
{
array2.pop();
array2.push();
array2.push(S(i));
delete array2;
delete array[0];
}
// array.length changes; array2.length doesn't change
for (uint i = 0; i < 7; i++)
{
for (uint j = i; j < array.length; j++)
{
for (uint k = 0; k < j; k++)
{
}
for (uint k = 0; k < array2.length; k++) // warning should appear
{
array.pop();
}
}
}
// array.length doesn't change; array2.length changes
for (uint i = 0; i < 7; i++)
{
for (uint j = i; j < array.length; j++) // warning should appear
{
for (uint k = 0; k < j; k++)
{
}
for (uint k = 0; k < array2.length; k++)
{
array2.pop();
}
}
}
// none of array.length and array2.length changes
for (uint i = 0; i < 7; i++)
{
for (uint j = i; j < array.length; j++) // warning should appear
{
for (uint k = 0; k < j; k++)
{
}
for (uint k = 0; k < array2.length; k++) // warning should appear
{
}
}
}
S[] memory array3;
// array3 not modified, but it's not a storage array
for (uint i = 0; i < array3.length; i++)
{
}
// array not modified, but it may potentially change in an internal function call
for (uint i = 0; i < array.length; i++)
{
g();
}
// array not modified, but it may potentially change in an external function call
for (uint i = 0; i < array.length; i++)
{
this.h();
}
// array not modified and it cannot be changed in a function call since g_view is a view function
for (uint i = 0; i < array.length; i++) // warning should appear
{
g_view();
}
// array not modified and it cannot be changed in a function call since h_view is a view function
for (uint i = 0; i < array.length; i++) // warning should appear
{
this.h_view();
}
}
}

@ -56,7 +56,7 @@ def id_test(test_item: Test):
return f"{test_item.detector.__name__}-{test_item.solc_ver}-{test_item.test_file}"
ALL_TEST_OBJECTS = [
ALL_TESTS = [
Test(
all_detectors.UninitializedFunctionPtrsConstructor,
"uninitialized_function_ptr_constructor.sol",
@ -1639,6 +1639,11 @@ ALL_TEST_OBJECTS = [
"LowCyclomaticComplexity.sol",
"0.8.16",
),
Test(
all_detectors.CacheArrayLength,
"CacheArrayLength.sol",
"0.8.17",
),
Test(
all_detectors.IncorrectUsingFor,
"IncorrectUsingForTopLevel.sol",
@ -1656,7 +1661,7 @@ GENERIC_PATH = "/GENERIC_PATH"
TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data"
# pylint: disable=too-many-locals
@pytest.mark.parametrize("test_item", ALL_TEST_OBJECTS, ids=id_test)
@pytest.mark.parametrize("test_item", ALL_TESTS, ids=id_test)
def test_detector(test_item: Test, snapshot):
test_dir_path = Path(
TEST_DATA_DIR,
@ -1704,5 +1709,5 @@ if __name__ == "__main__":
"To generate the zip artifacts run\n\tpython tests/e2e/tests/test_detectors.py --compile"
)
elif sys.argv[1] == "--compile":
for next_test in ALL_TEST_OBJECTS:
for next_test in ALL_TESTS:
_generate_compile(next_test, skip_existing=True)

@ -458,6 +458,7 @@ ALL_TESTS = [
"assembly-functions.sol",
["0.6.9", "0.7.6", "0.8.16"],
),
Test("user_defined_operators-0.8.19.sol", ["0.8.19"]),
]
# create the output folder if needed
try:

@ -0,0 +1,13 @@
{
"Lib": {
"f(Int)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n}\n"
},
"T": {
"add_function_call(Int,Int)": "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: RETURN 2\n\"];\n}\n",
"add_op(Int,Int)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n",
"lib_call(Int)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n",
"neg_usertype(Int)": "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: RETURN 2\n\"];\n}\n",
"neg_int(int256)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n",
"eq_op(Int,Int)": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: RETURN 1\n\"];\n}\n"
}
}

@ -0,0 +1,48 @@
pragma solidity ^0.8.19;
type Int is int;
using {add as +, eq as ==, add, neg as -, Lib.f} for Int global;
function add(Int a, Int b) pure returns (Int) {
return Int.wrap(Int.unwrap(a) + Int.unwrap(b));
}
function eq(Int a, Int b) pure returns (bool) {
return true;
}
function neg(Int a) pure returns (Int) {
return a;
}
library Lib {
function f(Int r) internal {}
}
contract T {
function add_function_call(Int b, Int c) public returns(Int) {
Int res = add(b,c);
return res;
}
function add_op(Int b, Int c) public returns(Int) {
return b + c;
}
function lib_call(Int b) public {
return b.f();
}
function neg_usertype(Int b) public returns(Int) {
Int res = -b;
return res;
}
function neg_int(int b) public returns(int) {
return -b;
}
function eq_op(Int b, Int c) public returns(bool) {
return b == c;
}
}

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

Loading…
Cancel
Save