mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #9915 from ethereum/develop
Merge develop into release for 0.7.2
This commit is contained in:
commit
51b20bc087
18
.circleci/build_win.ps1
Normal file
18
.circleci/build_win.ps1
Normal file
@ -0,0 +1,18 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
cd "$PSScriptRoot\.."
|
||||
|
||||
if ("$Env:FORCE_RELEASE") {
|
||||
New-Item prerelease.txt -type file
|
||||
Write-Host "Building release version."
|
||||
}
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
$boost_dir=(Resolve-Path $PSScriptRoot\..\deps\boost\lib\cmake\Boost-*)
|
||||
..\deps\cmake\bin\cmake -G "Visual Studio 16 2019" -DBoost_DIR="$boost_dir\" -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DCMAKE_INSTALL_PREFIX="$PSScriptRoot\..\upload" ..
|
||||
if ( -not $? ) { throw "CMake configure failed." }
|
||||
msbuild solidity.sln /p:Configuration=Release /m:5 /v:minimal
|
||||
if ( -not $? ) { throw "Build failed." }
|
||||
..\deps\cmake\bin\cmake --build . -j 5 --target install --config Release
|
||||
if ( -not $? ) { throw "Install target failed." }
|
@ -9,23 +9,26 @@ version: 2.1
|
||||
parameters:
|
||||
ubuntu-1804-docker-image:
|
||||
type: string
|
||||
# solbuildpackpusher/solidity-buildpack-deps:ubuntu1804-2
|
||||
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:9ab317e583c395e50884ba82e9f99811c374344cea4c550725be8ec836e07acc"
|
||||
# solbuildpackpusher/solidity-buildpack-deps:ubuntu1804-3
|
||||
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:19f613d2ac47fedff654dacef984d8a64726c4d67ae8f2667a85ee7d97ac4c1c"
|
||||
ubuntu-2004-docker-image:
|
||||
type: string
|
||||
# solbuildpackpusher/solidity-buildpack-deps:ubuntu2004-2
|
||||
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:cbfa42d8ecbe94391ba8837e218869242666de7a0da6ccac065a856c85b6a6a0"
|
||||
# solbuildpackpusher/solidity-buildpack-deps:ubuntu2004-3
|
||||
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:aeedbe7390a7383815f0cf0f8a1b8bf84dc5e334a3b0043ebcdf8b1bdbe80a81"
|
||||
ubuntu-2004-clang-docker-image:
|
||||
type: string
|
||||
# solbuildpackpusher/solidity-buildpack-deps:ubuntu2004.clang-2
|
||||
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:7a4d5271b5552139d9f2caefc50d42f401bf74132cf8f253e199e11c80ab42de"
|
||||
# solbuildpackpusher/solidity-buildpack-deps:ubuntu2004.clang-3
|
||||
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:2593c15689dee5b5bdfff96a36c8c68a468cd3b147c41f75b820b8fabc257be9"
|
||||
ubuntu-1604-clang-ossfuzz-docker-image:
|
||||
type: string
|
||||
# solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-3
|
||||
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:6fa6914bd81abcac4b162c738e6ff05d87cefe7655e3859c7a827e5a8ec20dc7"
|
||||
# solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-4
|
||||
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:842126b164b3542f05bff2611459e21edc7e3e2c81ca9d1f43396c8cf066f7ca"
|
||||
emscripten-docker-image:
|
||||
type: string
|
||||
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:d557d015918c3cf68b0d22839bab41013f0757b651a7fef21595f89721dbebcc"
|
||||
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:23dad3b34deae8107c8551804ef299f6a89c23ed506e8118fac151e2bdc9018c"
|
||||
|
||||
orbs:
|
||||
win: circleci/windows@2.2.0
|
||||
|
||||
defaults:
|
||||
|
||||
@ -64,6 +67,10 @@ defaults:
|
||||
path: build/solc/solc
|
||||
destination: solc
|
||||
|
||||
# windows artifacts
|
||||
- artifact_solc_windows: &artifact_solc_windows
|
||||
path: upload/
|
||||
|
||||
# compiled tool executable target
|
||||
- artifacts_tools: &artifacts_tools
|
||||
path: build/tools/solidity-upgrade
|
||||
@ -107,14 +114,17 @@ defaults:
|
||||
|
||||
- run_soltest: &run_soltest
|
||||
name: soltest
|
||||
no_output_timeout: 30m
|
||||
command: ./.circleci/soltest.sh
|
||||
|
||||
- run_soltest_all: &run_soltest_all
|
||||
name: soltest_all
|
||||
no_output_timeout: 30m
|
||||
command: ./.circleci/soltest_all.sh
|
||||
|
||||
- run_cmdline_tests: &run_cmdline_tests
|
||||
name: command line tests
|
||||
no_output_timeout: 30m
|
||||
command: ./test/cmdlineTests.sh
|
||||
|
||||
- run_docs_pragma_min_version: &run_docs_pragma_min_version
|
||||
@ -163,7 +173,6 @@ defaults:
|
||||
at: build
|
||||
- run:
|
||||
<<: *run_soltest
|
||||
no_output_timeout: 30m
|
||||
- store_test_results: *store_test_results
|
||||
- store_artifacts: *artifacts_test_results
|
||||
|
||||
@ -175,7 +184,6 @@ defaults:
|
||||
at: build
|
||||
- run:
|
||||
<<: *run_soltest
|
||||
no_output_timeout: 30m
|
||||
- store_test_results: *store_test_results
|
||||
- store_artifacts: *artifacts_test_results
|
||||
|
||||
@ -207,6 +215,11 @@ defaults:
|
||||
requires:
|
||||
- b_ubu_release
|
||||
|
||||
- workflow_archlinux: &workflow_archlinux
|
||||
<<: *workflow_trigger_on_tags
|
||||
requires:
|
||||
- b_archlinux
|
||||
|
||||
- workflow_ubuntu2004_codecov: &workflow_ubuntu2004_codecov
|
||||
<<: *workflow_trigger_on_tags
|
||||
requires:
|
||||
@ -237,6 +250,16 @@ defaults:
|
||||
requires:
|
||||
- b_ubu_ossfuzz
|
||||
|
||||
- workflow_win: &workflow_win
|
||||
<<: *workflow_trigger_on_tags
|
||||
requires:
|
||||
- b_win
|
||||
|
||||
- workflow_win_release: &workflow_win_release
|
||||
<<: *workflow_trigger_on_tags
|
||||
requires:
|
||||
- b_win_release
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Notification Templates
|
||||
- gitter_notify_failure: &gitter_notify_failure
|
||||
@ -656,6 +679,25 @@ jobs:
|
||||
t_ubu_soltest: &t_ubu_soltest
|
||||
<<: *test_ubuntu2004
|
||||
|
||||
t_archlinux_soltest: &t_archlinux_soltest
|
||||
docker:
|
||||
- image: archlinux/base
|
||||
environment:
|
||||
EVM: constantinople
|
||||
OPTIMIZE: 0
|
||||
TERM: xterm
|
||||
steps:
|
||||
- run:
|
||||
name: Install runtime dependencies
|
||||
command: |
|
||||
pacman --noconfirm -Syu --noprogressbar --needed base-devel boost cmake z3 cvc4 git openssh tar
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: build
|
||||
- run: *run_soltest
|
||||
- store_test_results: *store_test_results
|
||||
- store_artifacts: *artifacts_test_results
|
||||
|
||||
t_ubu_soltest_enforce_yul: &t_ubu_soltest_enforce_yul
|
||||
docker:
|
||||
- image: << pipeline.parameters.ubuntu-2004-docker-image >>
|
||||
@ -709,7 +751,6 @@ jobs:
|
||||
at: build
|
||||
- run:
|
||||
<<: *run_cmdline_tests
|
||||
no_output_timeout: 30m
|
||||
- store_test_results: *store_test_results
|
||||
- store_artifacts: *artifacts_test_results
|
||||
|
||||
@ -745,6 +786,7 @@ jobs:
|
||||
apt-get install -qqy --no-install-recommends nodejs npm cvc4
|
||||
- run:
|
||||
name: Test solcjs
|
||||
no_output_timeout: 30m
|
||||
command: |
|
||||
node --version
|
||||
npm --version
|
||||
@ -851,6 +893,56 @@ jobs:
|
||||
- run: *gitter_notify_failure
|
||||
- run: *gitter_notify_success
|
||||
|
||||
b_win: &b_win
|
||||
executor:
|
||||
name: win/default
|
||||
shell: powershell.exe
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- dependencies-win-{{ checksum "scripts/install_deps.ps1" }}
|
||||
- run:
|
||||
name: "Installing dependencies"
|
||||
command: if ( -not (Test-Path .\deps\boost) ) { .\scripts\install_deps.ps1 }
|
||||
- save_cache:
|
||||
key: dependencies-win-{{ checksum "scripts/install_deps.ps1" }}
|
||||
paths:
|
||||
- .\deps\boost
|
||||
- .\deps\cmake
|
||||
- run:
|
||||
name: "Building solidity"
|
||||
command: .circleci/build_win.ps1
|
||||
- run:
|
||||
name: "Run solc.exe to make sure build was successful."
|
||||
command: .\build\solc\Release\solc.exe --version
|
||||
- store_artifacts: *artifact_solc_windows
|
||||
- persist_to_workspace: *artifacts_build_dir
|
||||
|
||||
b_win_release:
|
||||
<<: *b_win
|
||||
environment:
|
||||
FORCE_RELEASE: ON
|
||||
|
||||
t_win: &t_win
|
||||
executor:
|
||||
name: win/default
|
||||
shell: powershell.exe
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: build
|
||||
- run:
|
||||
name: "Install evmone"
|
||||
command: scripts/install_evmone.ps1
|
||||
- run:
|
||||
name: "Run soltest"
|
||||
command: .circleci/soltest.ps1
|
||||
- store_artifacts: *artifacts_test_results
|
||||
|
||||
t_win_release:
|
||||
<<: *t_win
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
@ -869,7 +961,6 @@ workflows:
|
||||
|
||||
# build-only
|
||||
- b_docs: *workflow_trigger_on_tags
|
||||
- b_archlinux: *workflow_trigger_on_tags
|
||||
- b_ubu_cxx20: *workflow_trigger_on_tags
|
||||
- b_ubu_ossfuzz: *workflow_trigger_on_tags
|
||||
|
||||
@ -878,6 +969,10 @@ workflows:
|
||||
- t_osx_cli: *workflow_osx
|
||||
- t_osx_soltest: *workflow_osx
|
||||
|
||||
# ArchLinux build and tests
|
||||
- b_archlinux: *workflow_trigger_on_tags
|
||||
- t_archlinux_soltest: *workflow_archlinux
|
||||
|
||||
# Ubuntu build and tests
|
||||
- b_ubu: *workflow_trigger_on_tags
|
||||
- b_ubu18: *workflow_trigger_on_tags
|
||||
@ -899,6 +994,12 @@ workflows:
|
||||
- t_ems_compile_ext_gnosis: *workflow_emscripten
|
||||
- t_ems_compile_ext_zeppelin: *workflow_emscripten
|
||||
|
||||
# Windows build and tests
|
||||
- b_win: *workflow_trigger_on_tags
|
||||
- b_win_release: *workflow_trigger_on_tags
|
||||
- t_win: *workflow_win
|
||||
- t_win_release: *workflow_win_release
|
||||
|
||||
nightly:
|
||||
|
||||
triggers:
|
||||
|
@ -43,13 +43,13 @@ then
|
||||
./scripts/install_obsolete_jsoncpp_1_7_4.sh
|
||||
|
||||
# z3
|
||||
wget https://github.com/Z3Prover/z3/releases/download/z3-4.8.8/z3-4.8.8-x64-osx-10.14.6.zip
|
||||
unzip z3-4.8.8-x64-osx-10.14.6.zip
|
||||
rm -f z3-4.8.8-x64-osx-10.14.6.zip
|
||||
cp z3-4.8.8-x64-osx-10.14.6/bin/libz3.a /usr/local/lib
|
||||
cp z3-4.8.8-x64-osx-10.14.6/bin/z3 /usr/local/bin
|
||||
cp z3-4.8.8-x64-osx-10.14.6/include/* /usr/local/include
|
||||
rm -rf z3-4.8.8-x64-osx-10.14.6
|
||||
wget https://github.com/Z3Prover/z3/releases/download/z3-4.8.9/z3-4.8.9-x64-osx-10.14.6.zip
|
||||
unzip z3-4.8.9-x64-osx-10.14.6.zip
|
||||
rm -f z3-4.8.9-x64-osx-10.14.6.zip
|
||||
cp z3-4.8.9-x64-osx-10.14.6/bin/libz3.a /usr/local/lib
|
||||
cp z3-4.8.9-x64-osx-10.14.6/bin/z3 /usr/local/bin
|
||||
cp z3-4.8.9-x64-osx-10.14.6/include/* /usr/local/include
|
||||
rm -rf z3-4.8.9-x64-osx-10.14.6
|
||||
|
||||
# evmone
|
||||
wget https://github.com/ethereum/evmone/releases/download/v0.4.0/evmone-0.4.0-darwin-x86_64.tar.gz
|
||||
|
12
.circleci/soltest.ps1
Executable file
12
.circleci/soltest.ps1
Executable file
@ -0,0 +1,12 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
cd "$PSScriptRoot\.."
|
||||
|
||||
.\build\solc\Release\solc.exe --version
|
||||
if ( -not $? ) { throw "Cannot execute solc --version." }
|
||||
|
||||
mkdir test_results
|
||||
.\build\test\Release\soltest.exe --color_output=no --show_progress=yes --logger=JUNIT,error,test_results/result.xml -- --no-smt
|
||||
if ( -not $? ) { throw "Unoptimized soltest run failed." }
|
||||
.\build\test\Release\soltest.exe --color_output=no --show_progress=yes --logger=JUNIT,error,test_results/result_opt.xml -- --optimize --no-smt
|
||||
if ( -not $? ) { throw "Optimized soltest run failed." }
|
@ -30,7 +30,7 @@ REPODIR="$(realpath $(dirname $0)/..)"
|
||||
|
||||
EVM_VALUES=(homestead byzantium constantinople petersburg istanbul)
|
||||
OPTIMIZE_VALUES=(0 1)
|
||||
STEPS=$(( 1 + ${#EVM_VALUES[@]} * ${#OPTIMIZE_VALUES[@]} ))
|
||||
STEPS=$(( 2 + ${#EVM_VALUES[@]} * ${#OPTIMIZE_VALUES[@]} ))
|
||||
|
||||
if (( $CIRCLE_NODE_TOTAL )) && (( $CIRCLE_NODE_TOTAL > 1 ))
|
||||
then
|
||||
@ -57,7 +57,12 @@ echo "Running steps $RUN_STEPS..."
|
||||
|
||||
STEP=1
|
||||
|
||||
[[ " $RUN_STEPS " =~ " $STEP " ]] && EVM=istanbul OPTIMIZE=1 ABI_ENCODER_V2=1 "${REPODIR}/.circleci/soltest.sh"
|
||||
# Run SMTChecker tests separately, as the heaviest expected run.
|
||||
[[ " $RUN_STEPS " =~ " $STEP " ]] && EVM=istanbul OPTIMIZE=1 ABI_ENCODER_V2=1 BOOST_TEST_ARGS="-t smtCheckerTests/*" "${REPODIR}/.circleci/soltest.sh"
|
||||
STEP=$(($STEP + 1))
|
||||
|
||||
# Run without SMTChecker tests.
|
||||
[[ " $RUN_STEPS " =~ " $STEP " ]] && EVM=istanbul OPTIMIZE=1 ABI_ENCODER_V2=1 BOOST_TEST_ARGS="-t !smtCheckerTests" "${REPODIR}/.circleci/soltest.sh"
|
||||
STEP=$(($STEP + 1))
|
||||
|
||||
for OPTIMIZE in ${OPTIMIZE_VALUES[@]}
|
||||
|
@ -113,7 +113,7 @@ matrix:
|
||||
before_install:
|
||||
- nvm install 10
|
||||
- nvm use 10
|
||||
- docker pull solbuildpackpusher/solidity-buildpack-deps@sha256:d557d015918c3cf68b0d22839bab41013f0757b651a7fef21595f89721dbebcc
|
||||
- docker pull solbuildpackpusher/solidity-buildpack-deps@sha256:23dad3b34deae8107c8551804ef299f6a89c23ed506e8118fac151e2bdc9018c
|
||||
env:
|
||||
- SOLC_EMSCRIPTEN=On
|
||||
- SOLC_INSTALL_DEPS_TRAVIS=Off
|
||||
|
@ -10,7 +10,7 @@ include(EthPolicy)
|
||||
eth_policy()
|
||||
|
||||
# project name and version should be set after cmake_policy CMP0048
|
||||
set(PROJECT_VERSION "0.7.1")
|
||||
set(PROJECT_VERSION "0.7.2")
|
||||
# OSX target needed in order to support std::visit
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
|
||||
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
|
||||
|
41
Changelog.md
41
Changelog.md
@ -1,3 +1,43 @@
|
||||
### 0.7.2 (2020-09-28)
|
||||
|
||||
Important Bugfixes:
|
||||
* Type Checker: Disallow two or more free functions with identical name (potentially imported and aliased) and parameter types.
|
||||
|
||||
|
||||
Compiler Features:
|
||||
* Export compiler-generated utility sources via standard-json or combined-json.
|
||||
* Optimizer: Optimize ``exp`` when base is 0, 1 or 2.
|
||||
* SMTChecker: Keep knowledge about string literals, even through assignment, and thus support the ``.length`` property properly.
|
||||
* SMTChecker: Support ``address`` type conversion with literals, e.g. ``address(0)``.
|
||||
* SMTChecker: Support ``revert()``.
|
||||
* SMTChecker: Support ``type(T).min``, ``type(T).max``, and ``type(I).interfaceId``.
|
||||
* SMTChecker: Support compound and, or, and xor operators.
|
||||
* SMTChecker: Support events and low-level logs.
|
||||
* SMTChecker: Support fixed bytes index access.
|
||||
* SMTChecker: Support memory allocation, e.g. ``new bytes(123)``.
|
||||
* SMTChecker: Support shifts.
|
||||
* SMTChecker: Support structs.
|
||||
* Type Checker: Explain why oversized hex string literals can not be explicitly converted to a shorter ``bytesNN`` type.
|
||||
* Type Checker: More detailed error messages why implicit conversions fail.
|
||||
* Type Checker: Report position of first invalid UTF-8 sequence in ``unicode""`` literals.
|
||||
* Yul IR Generator: Report source locations related to unimplemented features.
|
||||
* Yul Optimizer: Inline into functions further down in the call graph first.
|
||||
* Yul Optimizer: Prune unused parameters in functions.
|
||||
* Yul Optimizer: Try to simplify function names.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
* Code generator: Fix internal error on stripping dynamic types from return parameters on EVM versions without ``RETURNDATACOPY``.
|
||||
* Type Checker: Add missing check against nested dynamic arrays in ABI encoding functions when ABIEncoderV2 is disabled.
|
||||
* Type Checker: Correct the error message for invalid named parameter in a call to refer to the right argument.
|
||||
* Type Checker: Correct the warning for homonymous, but not shadowing declarations.
|
||||
* Type Checker: Disallow ``virtual`` for modifiers in libraries.
|
||||
* Type system: Fix internal error on implicit conversion of contract instance to the type of its ``super``.
|
||||
* Type system: Fix internal error on implicit conversion of string literal to a calldata string.
|
||||
* Type system: Fix named parameters in overloaded function and event calls being matched incorrectly if the order differs from the declaration.
|
||||
* ViewPureChecker: Prevent visibility check on constructors.
|
||||
|
||||
|
||||
### 0.7.1 (2020-09-02)
|
||||
|
||||
Language Features:
|
||||
@ -26,6 +66,7 @@ Bugfixes:
|
||||
* SMTChecker: Fix internal error on lvalue unary operators with tuples.
|
||||
* SMTChecker: Fix internal error on tuple assignment.
|
||||
* SMTChecker: Fix internal error on tuples of one element that have tuple type.
|
||||
* SMTChecker: Fix internal error when using imported code.
|
||||
* SMTChecker: Fix soundness of array ``pop``.
|
||||
* Type Checker: Disallow ``using for`` directive inside interfaces.
|
||||
* Type Checker: Disallow signed literals as exponent in exponentiation operator.
|
||||
|
@ -25,11 +25,13 @@
|
||||
- [ ] Create a pull request from ``develop`` to ``release``, wait for the tests, then merge it.
|
||||
- [ ] Make a final check that there are no platform-dependency issues in the ``solidity-test-bytecode`` repository.
|
||||
- [ ] Wait for the tests for the commit on ``release``, create a release in Github, creating the tag (click the `PUBLISH RELEASE` button on the release page.)
|
||||
- [ ] Wait for the CI runs on the tag itself (travis and appveyor should push artifacts onto the Github release page).
|
||||
- [ ] Wait for the CI runs on the tag itself (travis should push artifacts onto the Github release page).
|
||||
- [ ] Take the ``solc.exe`` binary from the ``b_win_release`` run of the released commit in circle-ci and add it to the release page as ``solc-windows.exe``.
|
||||
- [ ] Run ``scripts/create_source_tarball.sh`` while being on the tag to create the source tarball. Make sure to create ``prerelease.txt`` before: (``echo -n > prerelease.txt``). This will create the tarball in a directory called ``upload``.
|
||||
- [ ] Take the tarball from the upload directory (its name should be ``solidity_x.x.x.tar.gz``, otherwise ``prerelease.txt`` was missing in the step before) and upload the source tarball to the release page.
|
||||
|
||||
### Homebrew and MacOS
|
||||
- [ ] Update the version and the hash (``sha256sum solidity_x.x.x.tar.gz``) in https://github.com/Homebrew/homebrew-core/blob/master/Formula/solidity.rb
|
||||
- [ ] Update the version and the hash (``sha256sum solidity_x.x.x.tar.gz``) in https://github.com/ethereum/homebrew-ethereum/blob/master/solidity.rb
|
||||
- [ ] Take the binary from the ``b_osx`` run of the released commit in circle-ci and add it to the release page as ``solc-macos``.
|
||||
|
||||
|
@ -59,7 +59,7 @@ install:
|
||||
before_build:
|
||||
- if not exist build mkdir build
|
||||
- cd build
|
||||
- cmake -G "Visual Studio 15 2017 Win64" .. -DTESTS=On
|
||||
- cmake -G "Visual Studio 15 2017 Win64" .. -DTESTS=On -DBoost_USE_STATIC_RUNTIME=OFF
|
||||
build_script:
|
||||
- msbuild solidity.sln /p:Configuration=%CONFIGURATION% /m:%NUMBER_OF_PROCESSORS% /v:minimal
|
||||
- cd %APPVEYOR_BUILD_FOLDER%
|
||||
|
@ -25,6 +25,9 @@ set(ETH_SCRIPTS_DIR ${ETH_CMAKE_DIR}/scripts)
|
||||
## use multithreaded boost libraries, with -mt suffix
|
||||
set(Boost_USE_MULTITHREADED ON)
|
||||
option(Boost_USE_STATIC_LIBS "Link Boost statically" ON)
|
||||
if(WIN32)
|
||||
option(Boost_USE_STATIC_RUNTIME "Link Boost against static C++ runtime libraries" ON)
|
||||
endif()
|
||||
|
||||
set(BOOST_COMPONENTS "filesystem;unit_test_framework;program_options;system")
|
||||
|
||||
|
@ -15,5 +15,9 @@ macro (eth_policy)
|
||||
# do not interpret if() arguments as variables!
|
||||
cmake_policy(SET CMP0054 NEW)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
if (POLICY CMP0091)
|
||||
# Allow selecting MSVC runtime library using CMAKE_MSVC_RUNTIME_LIBRARY.
|
||||
cmake_policy(SET CMP0091 NEW)
|
||||
endif()
|
||||
endmacro()
|
||||
|
@ -34,6 +34,12 @@ if(CMAKE_VERSION VERSION_GREATER 3.1)
|
||||
set(byproducts BUILD_BYPRODUCTS "${JSONCPP_LIBRARY}")
|
||||
endif()
|
||||
|
||||
# Propagate CMAKE_MSVC_RUNTIME_LIBRARY on Windows builds, if set.
|
||||
if (WIN32 AND POLICY CMP0091 AND CMAKE_MSVC_RUNTIME_LIBRARY)
|
||||
list(APPEND JSONCPP_CMAKE_ARGS "-DCMAKE_POLICY_DEFAULT_CMP0091:STRING=NEW")
|
||||
list(APPEND JSONCPP_CMAKE_ARGS "-DCMAKE_MSVC_RUNTIME_LIBRARY=${CMAKE_MSVC_RUNTIME_LIBRARY}")
|
||||
endif()
|
||||
|
||||
ExternalProject_Add(jsoncpp-project
|
||||
PREFIX "${prefix}"
|
||||
DOWNLOAD_DIR "${CMAKE_SOURCE_DIR}/deps/downloads"
|
||||
@ -51,6 +57,7 @@ ExternalProject_Add(jsoncpp-project
|
||||
-DJSONCPP_WITH_PKGCONFIG_SUPPORT=OFF
|
||||
-DCMAKE_CXX_FLAGS=${JSONCPP_CXX_FLAGS}
|
||||
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
|
||||
${JSONCPP_CMAKE_ARGS}
|
||||
${byproducts}
|
||||
)
|
||||
|
||||
|
@ -537,7 +537,7 @@ For example,
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
|
||||
contract Test {
|
||||
|
@ -140,7 +140,7 @@ Local Solidity variables are available for assignments, for example:
|
||||
.. code::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract C {
|
||||
uint b;
|
||||
|
@ -70,6 +70,8 @@ Solidity Logo Guidelines
|
||||
.. image:: logo.svg
|
||||
:width: 256
|
||||
|
||||
*(Right click on the logo to download it.)*
|
||||
|
||||
Please do not:
|
||||
|
||||
- Change the ratio of the logo (do not stretch it or cut it).
|
||||
|
@ -1,4 +1,12 @@
|
||||
[
|
||||
{
|
||||
"name": "FreeFunctionRedefinition",
|
||||
"summary": "The compiler does not flag an error when two or more free functions with the same name and parameter types are defined in a source unit or when an imported free function alias shadows another free function with a different name but identical parameter types.",
|
||||
"description": "In contrast to functions defined inside contracts, free functions with identical names and parameter types did not create an error. Both definition of free functions with identical name and parameter types and an imported free function with an alias that shadows another function with a different name but identical parameter types were permitted due to which a call to either the multiply defined free function or the imported free function alias within a contract led to the execution of that free function which was defined first within the source unit. Subsequently defined identical free function definitions were silently ignored and their code generation was skipped.",
|
||||
"introduced": "0.7.1",
|
||||
"fixed": "0.7.2",
|
||||
"severity": "low"
|
||||
},
|
||||
{
|
||||
"name": "UsingForCalldata",
|
||||
"summary": "Function calls to internal library functions with calldata parameters called via ``using for`` can result in invalid data being read.",
|
||||
|
@ -1187,7 +1187,13 @@
|
||||
"released": "2020-07-28"
|
||||
},
|
||||
"0.7.1": {
|
||||
"bugs": [],
|
||||
"bugs": [
|
||||
"FreeFunctionRedefinition"
|
||||
],
|
||||
"released": "2020-09-02"
|
||||
},
|
||||
"0.7.2": {
|
||||
"bugs": [],
|
||||
"released": "2020-09-28"
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ you receive the funds of the person who is now the richest.
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract WithdrawalContract {
|
||||
address public richest;
|
||||
@ -62,7 +62,7 @@ This is as opposed to the more intuitive sending pattern:
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract SendContract {
|
||||
address payable public richest;
|
||||
|
@ -26,7 +26,7 @@ Not all types for constants and immutables are implemented at this time. The onl
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract C {
|
||||
uint constant X = 32**22 + 8;
|
||||
|
@ -277,7 +277,7 @@ neither a receive Ether nor a payable fallback function is present, the
|
||||
contract cannot receive Ether through regular transactions and throws an
|
||||
exception.
|
||||
|
||||
In the worst case, the fallback function can only rely on 2300 gas being
|
||||
In the worst case, the ``receive`` function can only rely on 2300 gas being
|
||||
available (for example when ``send`` or ``transfer`` is used), leaving little
|
||||
room to perform other operations except basic logging. The following operations
|
||||
will consume more gas than the 2300 gas stipend:
|
||||
|
@ -39,7 +39,7 @@ Details are given in the following example.
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
|
||||
contract Owned {
|
||||
@ -127,7 +127,7 @@ destruction request. The way this is done is problematic, as
|
||||
seen in the following example::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract owned {
|
||||
constructor() { owner = msg.sender; }
|
||||
@ -157,7 +157,7 @@ explicitly in the final override, but this function will bypass
|
||||
``Base1.destroy``. The way around this is to use ``super``::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract owned {
|
||||
constructor() { owner = msg.sender; }
|
||||
@ -214,7 +214,7 @@ The following example demonstrates changing mutability and visibility:
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract Base
|
||||
{
|
||||
@ -405,7 +405,7 @@ equivalent to ``constructor() {}``. For example:
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
abstract contract A {
|
||||
uint public a;
|
||||
@ -442,7 +442,7 @@ linearization rules explained below. If the base constructors have arguments,
|
||||
derived contracts need to specify all of them. This can be done in two ways::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract Base {
|
||||
uint x;
|
||||
@ -523,7 +523,7 @@ One area where inheritance linearization is especially important and perhaps not
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract Base1 {
|
||||
constructor() {}
|
||||
|
@ -2,22 +2,25 @@
|
||||
Contributing
|
||||
############
|
||||
|
||||
Help is always appreciated!
|
||||
Help is always welcome and there are plenty of options how you can contribute to Solidity.
|
||||
|
||||
To get started, you can try :ref:`building-from-source` in order to familiarize
|
||||
yourself with the components of Solidity and the build process. Also, it may be
|
||||
useful to become well-versed at writing smart-contracts in Solidity.
|
||||
In particular, we appreciate support in the following areas:
|
||||
|
||||
In particular, we need help in the following areas:
|
||||
|
||||
* Improving the documentation
|
||||
* Responding to questions from other users on `StackExchange
|
||||
<https://ethereum.stackexchange.com>`_ and the `Solidity Gitter
|
||||
<https://gitter.im/ethereum/solidity>`_
|
||||
* Reporting issues.
|
||||
* Fixing and responding to `Solidity's GitHub issues
|
||||
<https://github.com/ethereum/solidity/issues>`_, especially those tagged as
|
||||
`good first issue <https://github.com/ethereum/solidity/labels/good%20first%20issue>`_ which are
|
||||
meant as introductory issues for external contributors.
|
||||
* Improving the documentation.
|
||||
* Translating the documentation into more languages.
|
||||
* Responding to questions from other users on `StackExchange
|
||||
<https://ethereum.stackexchange.com>`_ and the `Solidity Gitter Chat
|
||||
<https://gitter.im/ethereum/solidity>`_.
|
||||
* Getting involved in the language design process by joining language design calls, proposing language changes or new features and providing feedback.
|
||||
|
||||
To get started, you can try :ref:`building-from-source` in order to familiarize
|
||||
yourself with the components of Solidity and the build process. Also, it may be
|
||||
useful to become well-versed at writing smart-contracts in Solidity.
|
||||
|
||||
Please note that this project is released with a `Contributor Code of Conduct <https://raw.githubusercontent.com/ethereum/solidity/develop/CODE_OF_CONDUCT.md>`_. By participating in this project - in the issues, pull requests, or Gitter channels - you agree to abide by its terms.
|
||||
|
||||
@ -27,8 +30,8 @@ Team Calls
|
||||
If you have issues or pull requests to discuss, or are interested in hearing what
|
||||
the team and contributors are working on, you can join our public team calls:
|
||||
|
||||
- Mondays at 12pm CET/CEST
|
||||
- Wednesdays at 2pm CET/CEST
|
||||
- Mondays at 12pm CET/CEST.
|
||||
- Wednesdays at 2pm CET/CEST.
|
||||
|
||||
Both calls take place on `Google Meet <https://meet.google.com/mrq-kbwv-edg>`_.
|
||||
|
||||
@ -39,12 +42,12 @@ To report an issue, please use the
|
||||
`GitHub issues tracker <https://github.com/ethereum/solidity/issues>`_. When
|
||||
reporting issues, please mention the following details:
|
||||
|
||||
* Which version of Solidity you are using
|
||||
* What was the source code (if applicable)
|
||||
* Which platform are you running on
|
||||
* How to reproduce the issue
|
||||
* What was the result of the issue
|
||||
* What the expected behaviour is
|
||||
* Which version of Solidity you are using.
|
||||
* What was the source code (if applicable).
|
||||
* Which platform are you running on.
|
||||
* How to reproduce the issue.
|
||||
* What was the result of the issue.
|
||||
* What the expected behaviour is.
|
||||
|
||||
Reducing the source code that caused the issue to a bare minimum is always
|
||||
very helpful and sometimes even clarifies a misunderstanding.
|
||||
@ -66,7 +69,7 @@ test cases under ``test/`` (see below).
|
||||
|
||||
However, if you are making a larger change, please consult with the `Solidity Development Gitter channel
|
||||
<https://gitter.im/ethereum/solidity-dev>`_ (different from the one mentioned above, this one is
|
||||
focused on compiler and language development instead of language use) first.
|
||||
focused on compiler and language development instead of language usage) first.
|
||||
|
||||
New features and bugfixes should be added to the ``Changelog.md`` file: please
|
||||
follow the style of previous entries, when applicable.
|
||||
@ -78,7 +81,7 @@ ensure that it builds locally before submitting a pull request.
|
||||
|
||||
Thank you for your help!
|
||||
|
||||
Running the compiler tests
|
||||
Running the Compiler Tests
|
||||
==========================
|
||||
|
||||
Prerequisites
|
||||
@ -91,7 +94,7 @@ in the current directory, installed on the system level, or the ``deps`` folder
|
||||
in the project top level. The required file is called ``libevmone.so`` on Linux
|
||||
systems, ``evmone.dll`` on Windows systems and ``libevmone.dylib`` on macOS.
|
||||
|
||||
Running the tests
|
||||
Running the Tests
|
||||
-----------------
|
||||
|
||||
Solidity includes different types of tests, most of them bundled into the
|
||||
@ -155,7 +158,7 @@ you have access to functions and variables in which you can break or print with.
|
||||
The CI runs additional tests (including ``solc-js`` and testing third party Solidity
|
||||
frameworks) that require compiling the Emscripten target.
|
||||
|
||||
Writing and running syntax tests
|
||||
Writing and Running Syntax Tests
|
||||
--------------------------------
|
||||
|
||||
Syntax tests check that the compiler generates the correct error messages for invalid code
|
||||
@ -366,7 +369,7 @@ the string parameter ``name`` is non-empty.
|
||||
Documentation Style Guide
|
||||
=========================
|
||||
|
||||
The following are style recommendations specifically for documentation
|
||||
In the following section you find style recommendations specifically focusing on documentation
|
||||
contributions to Solidity.
|
||||
|
||||
English Language
|
||||
@ -394,10 +397,10 @@ title.
|
||||
|
||||
For example, the following are all correct:
|
||||
|
||||
* Title Case for Headings
|
||||
* For Headings Use Title Case
|
||||
* Local and State Variable Names
|
||||
* Order of Layout
|
||||
* Title Case for Headings.
|
||||
* For Headings Use Title Case.
|
||||
* Local and State Variable Names.
|
||||
* Order of Layout.
|
||||
|
||||
Expand Contractions
|
||||
-------------------
|
||||
@ -450,3 +453,21 @@ Running Documentation Tests
|
||||
|
||||
Make sure your contributions pass our documentation tests by running ``./scripts/docs.sh`` that installs dependencies
|
||||
needed for documentation and checks for any problems such as broken links or syntax issues.
|
||||
|
||||
Solidity Language Design
|
||||
========================
|
||||
|
||||
If you want to get involved in the language design process and share your ideas, please join the `solidity-users forum <https://groups.google.com/g/solidity-users>`_,
|
||||
where existing properties of the language and proposals for new features can be discussed.
|
||||
|
||||
We regularly host language design discussion calls, in which selected topics, issues or feature implementations are debated in detail. The invitation
|
||||
to those calls is shared via the aforementioned forum. We are also sharing feedback surveys and other language design relevant content in this forum.
|
||||
|
||||
For ad-hoc cases and questions you can reach out to us via the `Solidity-dev Gitter channel <https://gitter.im/ethereum/solidity-dev>`_, a
|
||||
dedicated chatroom for conversations around the Solidity compiler and language development.
|
||||
|
||||
You can follow the implementation status of new features in the `Solidity Github project <https://github.com/ethereum/solidity/projects/43>`_.
|
||||
Issues in the design backlog need further specification and will either be discussed in a language design call or in a regular team call. You can
|
||||
see the upcoming changes for the next breaking release by changing from the default branch (`develop`) to the `breaking branch <https://github.com/ethereum/solidity/tree/breaking>`_.
|
||||
|
||||
We are happy to hear your thoughts on how we can improve the language design process to be even more collaborative and transparent.
|
@ -19,7 +19,7 @@ Solidity also supports exception handling in the form of ``try``/``catch``-state
|
||||
but only for :ref:`external function calls <external-function-calls>` and
|
||||
contract creation calls.
|
||||
|
||||
Parentheses can *not* be omitted for conditionals, but curly brances can be omitted
|
||||
Parentheses can *not* be omitted for conditionals, but curly braces can be omitted
|
||||
around single-statement bodies.
|
||||
|
||||
Note that there is no type conversion from non-boolean to boolean types as
|
||||
@ -105,8 +105,12 @@ otherwise, the ``value`` option would not be available.
|
||||
parentheses at the end perform the actual call. So in this case, the
|
||||
function is not called and the ``value`` and ``gas`` settings are lost.
|
||||
|
||||
Function calls cause exceptions if the called contract does not exist (in the
|
||||
sense that the account does not contain code) or if the called contract itself
|
||||
Due to the fact that the EVM considers a call to a non-existing contract to
|
||||
always succeed, Solidity uses the ``extcodesize`` opcode to check that
|
||||
the contract that is about to be called actually exists (it contains code)
|
||||
and causes an exception if it does not.
|
||||
|
||||
Function calls also cause exceptions if the called contract itself
|
||||
throws an exception or goes out of gas.
|
||||
|
||||
.. warning::
|
||||
@ -188,7 +192,7 @@ is compiled so recursive creation-dependencies are not possible.
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract D {
|
||||
uint public x;
|
||||
@ -244,7 +248,7 @@ which only need to be created if there is a dispute.
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract D {
|
||||
uint public x;
|
||||
|
@ -25,7 +25,7 @@ to receive their money - contracts cannot activate themselves.
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract SimpleAuction {
|
||||
// Parameters of the auction. Times are either
|
||||
@ -186,7 +186,7 @@ invalid bids.
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract BlindAuction {
|
||||
struct Bid {
|
||||
|
@ -61,12 +61,11 @@ For a contract that fulfils payments, the signed message must include:
|
||||
2. The amount to be transferred.
|
||||
3. Protection against replay attacks.
|
||||
|
||||
A replay attack is when a signed message is reused to claim authorization for
|
||||
a second action.
|
||||
To avoid replay attacks we use the same as in Ethereum transactions
|
||||
themselves, a so-called nonce, which is the number of transactions sent by an
|
||||
account.
|
||||
The smart contract checks if a nonce is used multiple times.
|
||||
A replay attack is when a signed message is reused to claim
|
||||
authorization for a second action. To avoid replay attacks
|
||||
we use the same technique as in Ethereum transactions themselves,
|
||||
a so-called nonce, which is the number of transactions sent by
|
||||
an account. The smart contract checks if a nonce is used multiple times.
|
||||
|
||||
Another type of replay attack can occur when the owner
|
||||
deploys a ``ReceiverPays`` smart contract, makes some
|
||||
@ -143,7 +142,7 @@ The full contract
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract ReceiverPays {
|
||||
address owner = msg.sender;
|
||||
@ -340,7 +339,7 @@ The full contract
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract SimplePaymentChannel {
|
||||
address payable public sender; // The account sending payments.
|
||||
|
@ -26,7 +26,7 @@ you can use state machine-like constructs inside a contract.
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract Purchase {
|
||||
uint public value;
|
||||
@ -143,4 +143,4 @@ you can use state machine-like constructs inside a contract.
|
||||
|
||||
seller.transfer(3 * value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ of votes.
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
/// @title Voting with delegation.
|
||||
contract Ballot {
|
||||
|
@ -163,8 +163,13 @@ Homebrew formula directly from Github.
|
||||
View
|
||||
`solidity.rb commits on Github <https://github.com/ethereum/homebrew-ethereum/commits/master/solidity.rb>`_.
|
||||
|
||||
Follow the history links until you have a raw file link of a
|
||||
specific commit of ``solidity.rb``.
|
||||
Copy the commit hash of the version you want and check it out on your machine.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://github.com/ethereum/homebrew-ethereum.git
|
||||
cd homebrew-ethereum
|
||||
git checkout <your-hash-goes-here>
|
||||
|
||||
Install it using ``brew``:
|
||||
|
||||
@ -172,7 +177,7 @@ Install it using ``brew``:
|
||||
|
||||
brew unlink solidity
|
||||
# eg. Install 0.4.8
|
||||
brew install https://raw.githubusercontent.com/ethereum/homebrew-ethereum/77cce03da9f289e5a3ffe579840d3c5dc0a62717/solidity.rb
|
||||
brew install solidity.rb
|
||||
|
||||
Gentoo Linux has an `Ethereum overlay <https://overlays.gentoo.org/#ethereum>`_ that contains a solidity package.
|
||||
After the overlay is setup, ``solc`` can be installed in x86_64 architectures by:
|
||||
@ -225,7 +230,7 @@ The following C++ compilers and their minimum versions can build the Solidity co
|
||||
|
||||
- `GCC <https://gcc.gnu.org>`_, version 5+
|
||||
- `Clang <https://clang.llvm.org/>`_, version 3.4+
|
||||
- `MSVC <https://docs.microsoft.com/en-us/cpp/?view=vs-2019>`_, version 2017+
|
||||
- `MSVC <https://visualstudio.microsoft.com/vs/>`_, version 2019+
|
||||
|
||||
Prerequisites - macOS
|
||||
---------------------
|
||||
@ -257,29 +262,29 @@ You need to install the following dependencies for Windows builds of Solidity:
|
||||
+-----------------------------------+-------------------------------------------------------+
|
||||
| Software | Notes |
|
||||
+===================================+=======================================================+
|
||||
| `Visual Studio 2017 Build Tools`_ | C++ compiler |
|
||||
| `Visual Studio 2019 Build Tools`_ | C++ compiler |
|
||||
+-----------------------------------+-------------------------------------------------------+
|
||||
| `Visual Studio 2017`_ (Optional) | C++ compiler and dev environment. |
|
||||
| `Visual Studio 2019`_ (Optional) | C++ compiler and dev environment. |
|
||||
+-----------------------------------+-------------------------------------------------------+
|
||||
|
||||
If you already have one IDE and only need the compiler and libraries,
|
||||
you could install Visual Studio 2017 Build Tools.
|
||||
you could install Visual Studio 2019 Build Tools.
|
||||
|
||||
Visual Studio 2017 provides both IDE and necessary compiler and libraries.
|
||||
So if you have not got an IDE and prefer to develop solidity, Visual Studio 2017
|
||||
Visual Studio 2019 provides both IDE and necessary compiler and libraries.
|
||||
So if you have not got an IDE and prefer to develop solidity, Visual Studio 2019
|
||||
may be a choice for you to get everything setup easily.
|
||||
|
||||
Here is the list of components that should be installed
|
||||
in Visual Studio 2017 Build Tools or Visual Studio 2017:
|
||||
in Visual Studio 2019 Build Tools or Visual Studio 2019:
|
||||
|
||||
* Visual Studio C++ core features
|
||||
* VC++ 2017 v141 toolset (x86,x64)
|
||||
* VC++ 2019 v141 toolset (x86,x64)
|
||||
* Windows Universal CRT SDK
|
||||
* Windows 8.1 SDK
|
||||
* C++/CLI support
|
||||
|
||||
.. _Visual Studio 2017: https://www.visualstudio.com/vs/
|
||||
.. _Visual Studio 2017 Build Tools: https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2017
|
||||
.. _Visual Studio 2019: https://www.visualstudio.com/vs/
|
||||
.. _Visual Studio 2019 Build Tools: https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2019
|
||||
|
||||
Dependencies Helper Script
|
||||
--------------------------
|
||||
@ -295,7 +300,10 @@ Or, on Windows:
|
||||
|
||||
.. code-block:: bat
|
||||
|
||||
scripts\install_deps.bat
|
||||
scripts\install_deps.ps1
|
||||
|
||||
Note that the latter command will install ``boost`` and ``cmake`` to the ``deps`` subdirectory, while the former command
|
||||
will attempt to install the dependencies globally.
|
||||
|
||||
Clone the Repository
|
||||
--------------------
|
||||
@ -357,11 +365,14 @@ And for Windows:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -G "Visual Studio 15 2017 Win64" ..
|
||||
cmake -G "Visual Studio 16 2019 Win64" ..
|
||||
|
||||
This latter set of instructions should result in the creation of
|
||||
**solidity.sln** in that build directory. Double-clicking on that file
|
||||
should result in Visual Studio firing up. We suggest building
|
||||
In case you want to use the version of boost installed by ``./scripts/install_deps.ps1``, you will
|
||||
additionally need to pass ``-DBoost_DIR="..\deps\boost\lib\cmake\Boost-*"`` and ``-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded``
|
||||
as arguments to the call to ``cmake``.
|
||||
|
||||
This should result in the creation of **solidity.sln** in that build directory.
|
||||
Double-clicking on that file should result in Visual Studio firing up. We suggest building
|
||||
**Release** configuration, but all others work.
|
||||
|
||||
Alternatively, you can build for Windows on the command-line, like so:
|
||||
|
@ -201,7 +201,7 @@ Never use tx.origin for authorization. Let's say you have a wallet contract like
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
|
||||
contract TxUserWallet {
|
||||
@ -222,7 +222,7 @@ Now someone tricks you into sending Ether to the address of this attack wallet:
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
interface TxUserWallet {
|
||||
function transferTo(address payable dest, uint amount) external;
|
||||
|
@ -300,7 +300,7 @@ Within a grouping, place the ``view`` and ``pure`` functions last.
|
||||
Yes::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract A {
|
||||
constructor() {
|
||||
@ -337,7 +337,7 @@ Yes::
|
||||
No::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract A {
|
||||
|
||||
@ -758,7 +758,7 @@ manner as modifiers if the function declaration is long or hard to read.
|
||||
Yes::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
// Base contracts just to make this compile
|
||||
contract B {
|
||||
@ -790,7 +790,7 @@ Yes::
|
||||
No::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
|
||||
// Base contracts just to make this compile
|
||||
@ -1012,7 +1012,7 @@ As shown in the example below, if the contract name is ``Congress`` and the libr
|
||||
Yes::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
|
||||
// Owned.sol
|
||||
@ -1048,7 +1048,7 @@ and in ``Congress.sol``::
|
||||
No::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
|
||||
// owned.sol
|
||||
|
@ -434,7 +434,7 @@ Array slices are useful to ABI-decode secondary data passed in function paramete
|
||||
::
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
|
||||
contract Proxy {
|
||||
/// @dev Address of the client contract managed by proxy i.e., this contract
|
||||
|
@ -314,6 +314,7 @@ Input Description
|
||||
// evm.bytecode.opcodes - Opcodes list
|
||||
// evm.bytecode.sourceMap - Source mapping (useful for debugging)
|
||||
// evm.bytecode.linkReferences - Link references (if unlinked object)
|
||||
// evm.bytecode.generatedSources - Sources generated by the compiler
|
||||
// evm.deployedBytecode* - Deployed bytecode (has all the options that evm.bytecode has)
|
||||
// evm.deployedBytecode.immutableReferences - Map from AST ids to bytecode ranges that reference immutables
|
||||
// evm.methodIdentifiers - The list of function hashes
|
||||
@ -427,6 +428,18 @@ Output Description
|
||||
"opcodes": "",
|
||||
// The source mapping as a string. See the source mapping definition.
|
||||
"sourceMap": "",
|
||||
// Array of sources generated by the compiler. Currently only
|
||||
// contains a single Yul file.
|
||||
"generatedSources": [{
|
||||
// Yul AST
|
||||
"ast": { ... }
|
||||
// Source file in its text form (may contain comments)
|
||||
"contents":"{ function abi_decode(start, end) -> data { data := calldataload(start) } }",
|
||||
// Source file ID, used for source references, same "namespace" as the Solidity source files
|
||||
"id": 2,
|
||||
"language": "Yul",
|
||||
"name": "#utility.yul"
|
||||
}]
|
||||
// If given, this is an unlinked object.
|
||||
"linkReferences": {
|
||||
"libraryFile.sol": {
|
||||
@ -693,7 +706,7 @@ have to be updated manually.)
|
||||
|
||||
.. code-block:: Solidity
|
||||
|
||||
pragma solidity >0.6.99 <0.8.0;
|
||||
pragma solidity ^0.7.0;
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
abstract contract C {
|
||||
// FIXME: remove constructor visibility and make the contract abstract
|
||||
|
27
docs/yul.rst
27
docs/yul.rst
@ -243,7 +243,7 @@ Since variables are stored on the stack, they do not directly
|
||||
influence memory or storage, but they can be used as pointers
|
||||
to memory or storage locations in the built-in functions
|
||||
``mstore``, ``mload``, ``sstore`` and ``sload``.
|
||||
Future dialects migh introduce specific types for such pointers.
|
||||
Future dialects might introduce specific types for such pointers.
|
||||
|
||||
When a variable is referenced, its current value is copied.
|
||||
For the EVM, this translates to a ``DUP`` instruction.
|
||||
@ -952,6 +952,26 @@ option.
|
||||
|
||||
See :ref:`Using the Commandline Compiler <commandline-compiler>` for details about the Solidity linker.
|
||||
|
||||
memoryguard
|
||||
^^^^^^^^^^^
|
||||
|
||||
This function is available in the EVM dialect with objects. The caller of
|
||||
``let ptr := memoryguard(size)`` (where ``size`` has to be a literal number)
|
||||
promises that they only use memory in either the range ``[0, size)`` or the
|
||||
unbounded range starting at ``ptr``.
|
||||
|
||||
Since the presence of a ``memoryguard`` call indicates that all memory access
|
||||
adheres to this restriction, it allows the optimizer to perform additional
|
||||
optimization steps, for example the stack limit evader, which attempts to move
|
||||
stack variables that would otherwise be unreachable to memory.
|
||||
|
||||
The Yul optimizer promises to only use the memory range ``[size, ptr)`` for its purposes.
|
||||
If the optimizer does not need to reserve any memory, it holds that ``ptr == size``.
|
||||
|
||||
``memoryguard`` can be called multiple times, but needs to have the same literal as argument
|
||||
within one Yul subobject. If at least one ``memoryguard`` call is found in a subobject,
|
||||
the additional optimiser steps will be run on it.
|
||||
|
||||
|
||||
.. _yul-object:
|
||||
|
||||
@ -1110,6 +1130,7 @@ Abbreviation Full name
|
||||
``L`` ``LoadResolver``
|
||||
``M`` ``LoopInvariantCodeMotion``
|
||||
``r`` ``RedundantAssignEliminator``
|
||||
``R`` ``ReasoningBasedSimplifier`` - highly experimental
|
||||
``m`` ``Rematerialiser``
|
||||
``V`` ``SSAReverser``
|
||||
``a`` ``SSATransform``
|
||||
@ -1121,6 +1142,10 @@ Abbreviation Full name
|
||||
Some steps depend on properties ensured by ``BlockFlattener``, ``FunctionGrouper``, ``ForLoopInitRewriter``.
|
||||
For this reason the Yul optimizer always applies them before applying any steps supplied by the user.
|
||||
|
||||
The ReasoningBasedSimplifier is an optimizer step that is currently not enabled
|
||||
in the default set of steps. It uses an SMT solver to simplify arithmetic expressions
|
||||
and boolean conditions. It has not received thorough testing or validation yet and can produce
|
||||
non-reproducible results, so please use with care!
|
||||
|
||||
.. _erc20yul:
|
||||
|
||||
|
@ -71,51 +71,51 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart1(
|
||||
{
|
||||
using Word = typename Pattern::Word;
|
||||
using Builtins = typename Pattern::Builtins;
|
||||
return std::vector<SimplificationRule<Pattern>> {
|
||||
return std::vector<SimplificationRule<Pattern>>{
|
||||
// arithmetic on constants
|
||||
{Builtins::ADD(A, B), [=]{ return A.d() + B.d(); }, false},
|
||||
{Builtins::MUL(A, B), [=]{ return A.d() * B.d(); }, false},
|
||||
{Builtins::SUB(A, B), [=]{ return A.d() - B.d(); }, false},
|
||||
{Builtins::DIV(A, B), [=]{ return B.d() == 0 ? 0 : divWorkaround(A.d(), B.d()); }, false},
|
||||
{Builtins::SDIV(A, B), [=]{ return B.d() == 0 ? 0 : s2u(divWorkaround(u2s(A.d()), u2s(B.d()))); }, false},
|
||||
{Builtins::MOD(A, B), [=]{ return B.d() == 0 ? 0 : modWorkaround(A.d(), B.d()); }, false},
|
||||
{Builtins::SMOD(A, B), [=]{ return B.d() == 0 ? 0 : s2u(modWorkaround(u2s(A.d()), u2s(B.d()))); }, false},
|
||||
{Builtins::EXP(A, B), [=]{ return Word(boost::multiprecision::powm(bigint(A.d()), bigint(B.d()), bigint(1) << Pattern::WordSize)); }, false},
|
||||
{Builtins::NOT(A), [=]{ return ~A.d(); }, false},
|
||||
{Builtins::LT(A, B), [=]() -> Word { return A.d() < B.d() ? 1 : 0; }, false},
|
||||
{Builtins::GT(A, B), [=]() -> Word { return A.d() > B.d() ? 1 : 0; }, false},
|
||||
{Builtins::SLT(A, B), [=]() -> Word { return u2s(A.d()) < u2s(B.d()) ? 1 : 0; }, false},
|
||||
{Builtins::SGT(A, B), [=]() -> Word { return u2s(A.d()) > u2s(B.d()) ? 1 : 0; }, false},
|
||||
{Builtins::EQ(A, B), [=]() -> Word { return A.d() == B.d() ? 1 : 0; }, false},
|
||||
{Builtins::ISZERO(A), [=]() -> Word { return A.d() == 0 ? 1 : 0; }, false},
|
||||
{Builtins::AND(A, B), [=]{ return A.d() & B.d(); }, false},
|
||||
{Builtins::OR(A, B), [=]{ return A.d() | B.d(); }, false},
|
||||
{Builtins::XOR(A, B), [=]{ return A.d() ^ B.d(); }, false},
|
||||
{Builtins::ADD(A, B), [=]{ return A.d() + B.d(); }},
|
||||
{Builtins::MUL(A, B), [=]{ return A.d() * B.d(); }},
|
||||
{Builtins::SUB(A, B), [=]{ return A.d() - B.d(); }},
|
||||
{Builtins::DIV(A, B), [=]{ return B.d() == 0 ? 0 : divWorkaround(A.d(), B.d()); }},
|
||||
{Builtins::SDIV(A, B), [=]{ return B.d() == 0 ? 0 : s2u(divWorkaround(u2s(A.d()), u2s(B.d()))); }},
|
||||
{Builtins::MOD(A, B), [=]{ return B.d() == 0 ? 0 : modWorkaround(A.d(), B.d()); }},
|
||||
{Builtins::SMOD(A, B), [=]{ return B.d() == 0 ? 0 : s2u(modWorkaround(u2s(A.d()), u2s(B.d()))); }},
|
||||
{Builtins::EXP(A, B), [=]{ return Word(boost::multiprecision::powm(bigint(A.d()), bigint(B.d()), bigint(1) << Pattern::WordSize)); }},
|
||||
{Builtins::NOT(A), [=]{ return ~A.d(); }},
|
||||
{Builtins::LT(A, B), [=]() -> Word { return A.d() < B.d() ? 1 : 0; }},
|
||||
{Builtins::GT(A, B), [=]() -> Word { return A.d() > B.d() ? 1 : 0; }},
|
||||
{Builtins::SLT(A, B), [=]() -> Word { return u2s(A.d()) < u2s(B.d()) ? 1 : 0; }},
|
||||
{Builtins::SGT(A, B), [=]() -> Word { return u2s(A.d()) > u2s(B.d()) ? 1 : 0; }},
|
||||
{Builtins::EQ(A, B), [=]() -> Word { return A.d() == B.d() ? 1 : 0; }},
|
||||
{Builtins::ISZERO(A), [=]() -> Word { return A.d() == 0 ? 1 : 0; }},
|
||||
{Builtins::AND(A, B), [=]{ return A.d() & B.d(); }},
|
||||
{Builtins::OR(A, B), [=]{ return A.d() | B.d(); }},
|
||||
{Builtins::XOR(A, B), [=]{ return A.d() ^ B.d(); }},
|
||||
{Builtins::BYTE(A, B), [=]{
|
||||
return
|
||||
A.d() >= Pattern::WordSize / 8 ?
|
||||
0 :
|
||||
(B.d() >> unsigned(8 * (Pattern::WordSize / 8 - 1 - A.d()))) & 0xff;
|
||||
}, false},
|
||||
{Builtins::ADDMOD(A, B, C), [=]{ return C.d() == 0 ? 0 : Word((bigint(A.d()) + bigint(B.d())) % C.d()); }, false},
|
||||
{Builtins::MULMOD(A, B, C), [=]{ return C.d() == 0 ? 0 : Word((bigint(A.d()) * bigint(B.d())) % C.d()); }, false},
|
||||
}},
|
||||
{Builtins::ADDMOD(A, B, C), [=]{ return C.d() == 0 ? 0 : Word((bigint(A.d()) + bigint(B.d())) % C.d()); }},
|
||||
{Builtins::MULMOD(A, B, C), [=]{ return C.d() == 0 ? 0 : Word((bigint(A.d()) * bigint(B.d())) % C.d()); }},
|
||||
{Builtins::SIGNEXTEND(A, B), [=]() -> Word {
|
||||
if (A.d() >= Pattern::WordSize / 8 - 1)
|
||||
return B.d();
|
||||
unsigned testBit = unsigned(A.d()) * 8 + 7;
|
||||
Word mask = (Word(1) << testBit) - 1;
|
||||
return boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask;
|
||||
}, false},
|
||||
}},
|
||||
{Builtins::SHL(A, B), [=]{
|
||||
if (A.d() >= Pattern::WordSize)
|
||||
return Word(0);
|
||||
return shlWorkaround(B.d(), unsigned(A.d()));
|
||||
}, false},
|
||||
}},
|
||||
{Builtins::SHR(A, B), [=]{
|
||||
if (A.d() >= Pattern::WordSize)
|
||||
return Word(0);
|
||||
return B.d() >> unsigned(A.d());
|
||||
}, false}
|
||||
}}
|
||||
};
|
||||
}
|
||||
|
||||
@ -133,48 +133,48 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart2(
|
||||
using Builtins = typename Pattern::Builtins;
|
||||
return std::vector<SimplificationRule<Pattern>> {
|
||||
// invariants involving known constants
|
||||
{Builtins::ADD(X, 0), [=]{ return X; }, false},
|
||||
{Builtins::ADD(0, X), [=]{ return X; }, false},
|
||||
{Builtins::SUB(X, 0), [=]{ return X; }, false},
|
||||
{Builtins::SUB(~Word(0), X), [=]() -> Pattern { return Builtins::NOT(X); }, false},
|
||||
{Builtins::MUL(X, 0), [=]{ return Word(0); }, true},
|
||||
{Builtins::MUL(0, X), [=]{ return Word(0); }, true},
|
||||
{Builtins::MUL(X, 1), [=]{ return X; }, false},
|
||||
{Builtins::MUL(1, X), [=]{ return X; }, false},
|
||||
{Builtins::MUL(X, Word(-1)), [=]() -> Pattern { return Builtins::SUB(0, X); }, false},
|
||||
{Builtins::MUL(Word(-1), X), [=]() -> Pattern { return Builtins::SUB(0, X); }, false},
|
||||
{Builtins::DIV(X, 0), [=]{ return Word(0); }, true},
|
||||
{Builtins::DIV(0, X), [=]{ return Word(0); }, true},
|
||||
{Builtins::DIV(X, 1), [=]{ return X; }, false},
|
||||
{Builtins::SDIV(X, 0), [=]{ return Word(0); }, true},
|
||||
{Builtins::SDIV(0, X), [=]{ return Word(0); }, true},
|
||||
{Builtins::SDIV(X, 1), [=]{ return X; }, false},
|
||||
{Builtins::AND(X, ~Word(0)), [=]{ return X; }, false},
|
||||
{Builtins::AND(~Word(0), X), [=]{ return X; }, false},
|
||||
{Builtins::AND(X, 0), [=]{ return Word(0); }, true},
|
||||
{Builtins::AND(0, X), [=]{ return Word(0); }, true},
|
||||
{Builtins::OR(X, 0), [=]{ return X; }, false},
|
||||
{Builtins::OR(0, X), [=]{ return X; }, false},
|
||||
{Builtins::OR(X, ~Word(0)), [=]{ return ~Word(0); }, true},
|
||||
{Builtins::OR(~Word(0), X), [=]{ return ~Word(0); }, true},
|
||||
{Builtins::XOR(X, 0), [=]{ return X; }, false},
|
||||
{Builtins::XOR(0, X), [=]{ return X; }, false},
|
||||
{Builtins::MOD(X, 0), [=]{ return Word(0); }, true},
|
||||
{Builtins::MOD(0, X), [=]{ return Word(0); }, true},
|
||||
{Builtins::EQ(X, 0), [=]() -> Pattern { return Builtins::ISZERO(X); }, false },
|
||||
{Builtins::EQ(0, X), [=]() -> Pattern { return Builtins::ISZERO(X); }, false },
|
||||
{Builtins::SHL(0, X), [=]{ return X; }, false},
|
||||
{Builtins::SHR(0, X), [=]{ return X; }, false},
|
||||
{Builtins::SHL(X, 0), [=]{ return Word(0); }, true},
|
||||
{Builtins::SHR(X, 0), [=]{ return Word(0); }, true},
|
||||
{Builtins::GT(X, 0), [=]() -> Pattern { return Builtins::ISZERO(Builtins::ISZERO(X)); }, false},
|
||||
{Builtins::LT(0, X), [=]() -> Pattern { return Builtins::ISZERO(Builtins::ISZERO(X)); }, false},
|
||||
{Builtins::GT(X, ~Word(0)), [=]{ return Word(0); }, true},
|
||||
{Builtins::LT(~Word(0), X), [=]{ return Word(0); }, true},
|
||||
{Builtins::GT(0, X), [=]{ return Word(0); }, true},
|
||||
{Builtins::LT(X, 0), [=]{ return Word(0); }, true},
|
||||
{Builtins::AND(Builtins::BYTE(X, Y), Word(0xff)), [=]() -> Pattern { return Builtins::BYTE(X, Y); }, false},
|
||||
{Builtins::BYTE(Word(Pattern::WordSize / 8 - 1), X), [=]() -> Pattern { return Builtins::AND(X, Word(0xff)); }, false}
|
||||
{Builtins::ADD(X, 0), [=]{ return X; }},
|
||||
{Builtins::ADD(0, X), [=]{ return X; }},
|
||||
{Builtins::SUB(X, 0), [=]{ return X; }},
|
||||
{Builtins::SUB(~Word(0), X), [=]() -> Pattern { return Builtins::NOT(X); }},
|
||||
{Builtins::MUL(X, 0), [=]{ return Word(0); }},
|
||||
{Builtins::MUL(0, X), [=]{ return Word(0); }},
|
||||
{Builtins::MUL(X, 1), [=]{ return X; }},
|
||||
{Builtins::MUL(1, X), [=]{ return X; }},
|
||||
{Builtins::MUL(X, Word(-1)), [=]() -> Pattern { return Builtins::SUB(0, X); }},
|
||||
{Builtins::MUL(Word(-1), X), [=]() -> Pattern { return Builtins::SUB(0, X); }},
|
||||
{Builtins::DIV(X, 0), [=]{ return Word(0); }},
|
||||
{Builtins::DIV(0, X), [=]{ return Word(0); }},
|
||||
{Builtins::DIV(X, 1), [=]{ return X; }},
|
||||
{Builtins::SDIV(X, 0), [=]{ return Word(0); }},
|
||||
{Builtins::SDIV(0, X), [=]{ return Word(0); }},
|
||||
{Builtins::SDIV(X, 1), [=]{ return X; }},
|
||||
{Builtins::AND(X, ~Word(0)), [=]{ return X; }},
|
||||
{Builtins::AND(~Word(0), X), [=]{ return X; }},
|
||||
{Builtins::AND(X, 0), [=]{ return Word(0); }},
|
||||
{Builtins::AND(0, X), [=]{ return Word(0); }},
|
||||
{Builtins::OR(X, 0), [=]{ return X; }},
|
||||
{Builtins::OR(0, X), [=]{ return X; }},
|
||||
{Builtins::OR(X, ~Word(0)), [=]{ return ~Word(0); }},
|
||||
{Builtins::OR(~Word(0), X), [=]{ return ~Word(0); }},
|
||||
{Builtins::XOR(X, 0), [=]{ return X; }},
|
||||
{Builtins::XOR(0, X), [=]{ return X; }},
|
||||
{Builtins::MOD(X, 0), [=]{ return Word(0); }},
|
||||
{Builtins::MOD(0, X), [=]{ return Word(0); }},
|
||||
{Builtins::EQ(X, 0), [=]() -> Pattern { return Builtins::ISZERO(X); },},
|
||||
{Builtins::EQ(0, X), [=]() -> Pattern { return Builtins::ISZERO(X); },},
|
||||
{Builtins::SHL(0, X), [=]{ return X; }},
|
||||
{Builtins::SHR(0, X), [=]{ return X; }},
|
||||
{Builtins::SHL(X, 0), [=]{ return Word(0); }},
|
||||
{Builtins::SHR(X, 0), [=]{ return Word(0); }},
|
||||
{Builtins::GT(X, 0), [=]() -> Pattern { return Builtins::ISZERO(Builtins::ISZERO(X)); }},
|
||||
{Builtins::LT(0, X), [=]() -> Pattern { return Builtins::ISZERO(Builtins::ISZERO(X)); }},
|
||||
{Builtins::GT(X, ~Word(0)), [=]{ return Word(0); }},
|
||||
{Builtins::LT(~Word(0), X), [=]{ return Word(0); }},
|
||||
{Builtins::GT(0, X), [=]{ return Word(0); }},
|
||||
{Builtins::LT(X, 0), [=]{ return Word(0); }},
|
||||
{Builtins::AND(Builtins::BYTE(X, Y), Word(0xff)), [=]() -> Pattern { return Builtins::BYTE(X, Y); }},
|
||||
{Builtins::BYTE(Word(Pattern::WordSize / 8 - 1), X), [=]() -> Pattern { return Builtins::AND(X, Word(0xff)); }},
|
||||
};
|
||||
}
|
||||
|
||||
@ -191,16 +191,16 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart3(
|
||||
using Builtins = typename Pattern::Builtins;
|
||||
return std::vector<SimplificationRule<Pattern>> {
|
||||
// operations involving an expression and itself
|
||||
{Builtins::AND(X, X), [=]{ return X; }, true},
|
||||
{Builtins::OR(X, X), [=]{ return X; }, true},
|
||||
{Builtins::XOR(X, X), [=]{ return Word(0); }, true},
|
||||
{Builtins::SUB(X, X), [=]{ return Word(0); }, true},
|
||||
{Builtins::EQ(X, X), [=]{ return Word(1); }, true},
|
||||
{Builtins::LT(X, X), [=]{ return Word(0); }, true},
|
||||
{Builtins::SLT(X, X), [=]{ return Word(0); }, true},
|
||||
{Builtins::GT(X, X), [=]{ return Word(0); }, true},
|
||||
{Builtins::SGT(X, X), [=]{ return Word(0); }, true},
|
||||
{Builtins::MOD(X, X), [=]{ return Word(0); }, true}
|
||||
{Builtins::AND(X, X), [=]{ return X; }},
|
||||
{Builtins::OR(X, X), [=]{ return X; }},
|
||||
{Builtins::XOR(X, X), [=]{ return Word(0); }},
|
||||
{Builtins::SUB(X, X), [=]{ return Word(0); }},
|
||||
{Builtins::EQ(X, X), [=]{ return Word(1); }},
|
||||
{Builtins::LT(X, X), [=]{ return Word(0); }},
|
||||
{Builtins::SLT(X, X), [=]{ return Word(0); }},
|
||||
{Builtins::GT(X, X), [=]{ return Word(0); }},
|
||||
{Builtins::SGT(X, X), [=]{ return Word(0); }},
|
||||
{Builtins::MOD(X, X), [=]{ return Word(0); }}
|
||||
};
|
||||
}
|
||||
|
||||
@ -217,23 +217,23 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart4(
|
||||
using Builtins = typename Pattern::Builtins;
|
||||
return std::vector<SimplificationRule<Pattern>> {
|
||||
// logical instruction combinations
|
||||
{Builtins::NOT(Builtins::NOT(X)), [=]{ return X; }, false},
|
||||
{Builtins::XOR(X, Builtins::XOR(X, Y)), [=]{ return Y; }, true},
|
||||
{Builtins::XOR(X, Builtins::XOR(Y, X)), [=]{ return Y; }, true},
|
||||
{Builtins::XOR(Builtins::XOR(X, Y), X), [=]{ return Y; }, true},
|
||||
{Builtins::XOR(Builtins::XOR(Y, X), X), [=]{ return Y; }, true},
|
||||
{Builtins::OR(X, Builtins::AND(X, Y)), [=]{ return X; }, true},
|
||||
{Builtins::OR(X, Builtins::AND(Y, X)), [=]{ return X; }, true},
|
||||
{Builtins::OR(Builtins::AND(X, Y), X), [=]{ return X; }, true},
|
||||
{Builtins::OR(Builtins::AND(Y, X), X), [=]{ return X; }, true},
|
||||
{Builtins::AND(X, Builtins::OR(X, Y)), [=]{ return X; }, true},
|
||||
{Builtins::AND(X, Builtins::OR(Y, X)), [=]{ return X; }, true},
|
||||
{Builtins::AND(Builtins::OR(X, Y), X), [=]{ return X; }, true},
|
||||
{Builtins::AND(Builtins::OR(Y, X), X), [=]{ return X; }, true},
|
||||
{Builtins::AND(X, Builtins::NOT(X)), [=]{ return Word(0); }, true},
|
||||
{Builtins::AND(Builtins::NOT(X), X), [=]{ return Word(0); }, true},
|
||||
{Builtins::OR(X, Builtins::NOT(X)), [=]{ return ~Word(0); }, true},
|
||||
{Builtins::OR(Builtins::NOT(X), X), [=]{ return ~Word(0); }, true},
|
||||
{Builtins::NOT(Builtins::NOT(X)), [=]{ return X; }},
|
||||
{Builtins::XOR(X, Builtins::XOR(X, Y)), [=]{ return Y; }},
|
||||
{Builtins::XOR(X, Builtins::XOR(Y, X)), [=]{ return Y; }},
|
||||
{Builtins::XOR(Builtins::XOR(X, Y), X), [=]{ return Y; }},
|
||||
{Builtins::XOR(Builtins::XOR(Y, X), X), [=]{ return Y; }},
|
||||
{Builtins::OR(X, Builtins::AND(X, Y)), [=]{ return X; }},
|
||||
{Builtins::OR(X, Builtins::AND(Y, X)), [=]{ return X; }},
|
||||
{Builtins::OR(Builtins::AND(X, Y), X), [=]{ return X; }},
|
||||
{Builtins::OR(Builtins::AND(Y, X), X), [=]{ return X; }},
|
||||
{Builtins::AND(X, Builtins::OR(X, Y)), [=]{ return X; }},
|
||||
{Builtins::AND(X, Builtins::OR(Y, X)), [=]{ return X; }},
|
||||
{Builtins::AND(Builtins::OR(X, Y), X), [=]{ return X; }},
|
||||
{Builtins::AND(Builtins::OR(Y, X), X), [=]{ return X; }},
|
||||
{Builtins::AND(X, Builtins::NOT(X)), [=]{ return Word(0); }},
|
||||
{Builtins::AND(Builtins::NOT(X), X), [=]{ return Word(0); }},
|
||||
{Builtins::OR(X, Builtins::NOT(X)), [=]{ return ~Word(0); }},
|
||||
{Builtins::OR(Builtins::NOT(X), X), [=]{ return ~Word(0); }},
|
||||
};
|
||||
}
|
||||
|
||||
@ -249,14 +249,14 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart4_5(
|
||||
using Builtins = typename Pattern::Builtins;
|
||||
return std::vector<SimplificationRule<Pattern>>{
|
||||
// idempotent operations
|
||||
{Builtins::AND(Builtins::AND(X, Y), Y), [=]{ return Builtins::AND(X, Y); }, true},
|
||||
{Builtins::AND(Y, Builtins::AND(X, Y)), [=]{ return Builtins::AND(X, Y); }, true},
|
||||
{Builtins::AND(Builtins::AND(Y, X), Y), [=]{ return Builtins::AND(Y, X); }, true},
|
||||
{Builtins::AND(Y, Builtins::AND(Y, X)), [=]{ return Builtins::AND(Y, X); }, true},
|
||||
{Builtins::OR(Builtins::OR(X, Y), Y), [=]{ return Builtins::OR(X, Y); }, true},
|
||||
{Builtins::OR(Y, Builtins::OR(X, Y)), [=]{ return Builtins::OR(X, Y); }, true},
|
||||
{Builtins::OR(Builtins::OR(Y, X), Y), [=]{ return Builtins::OR(Y, X); }, true},
|
||||
{Builtins::OR(Y, Builtins::OR(Y, X)), [=]{ return Builtins::OR(Y, X); }, true},
|
||||
{Builtins::AND(Builtins::AND(X, Y), Y), [=]{ return Builtins::AND(X, Y); }},
|
||||
{Builtins::AND(Y, Builtins::AND(X, Y)), [=]{ return Builtins::AND(X, Y); }},
|
||||
{Builtins::AND(Builtins::AND(Y, X), Y), [=]{ return Builtins::AND(Y, X); }},
|
||||
{Builtins::AND(Y, Builtins::AND(Y, X)), [=]{ return Builtins::AND(Y, X); }},
|
||||
{Builtins::OR(Builtins::OR(X, Y), Y), [=]{ return Builtins::OR(X, Y); }},
|
||||
{Builtins::OR(Y, Builtins::OR(X, Y)), [=]{ return Builtins::OR(X, Y); }},
|
||||
{Builtins::OR(Builtins::OR(Y, X), Y), [=]{ return Builtins::OR(Y, X); }},
|
||||
{Builtins::OR(Y, Builtins::OR(Y, X)), [=]{ return Builtins::OR(Y, X); }},
|
||||
};
|
||||
}
|
||||
|
||||
@ -280,8 +280,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart5(
|
||||
Word value = Word(1) << i;
|
||||
rules.push_back({
|
||||
Builtins::MOD(X, value),
|
||||
[=]() -> Pattern { return Builtins::AND(X, value - 1); },
|
||||
false
|
||||
[=]() -> Pattern { return Builtins::AND(X, value - 1); }
|
||||
});
|
||||
}
|
||||
|
||||
@ -289,7 +288,6 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart5(
|
||||
rules.push_back({
|
||||
Builtins::SHL(A, X),
|
||||
[=]() -> Pattern { return Word(0); },
|
||||
true,
|
||||
[=]() { return A.d() >= Pattern::WordSize; }
|
||||
});
|
||||
|
||||
@ -297,7 +295,6 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart5(
|
||||
rules.push_back({
|
||||
Builtins::SHR(A, X),
|
||||
[=]() -> Pattern { return Word(0); },
|
||||
true,
|
||||
[=]() { return A.d() >= Pattern::WordSize; }
|
||||
});
|
||||
|
||||
@ -305,7 +302,6 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart5(
|
||||
rules.push_back({
|
||||
Builtins::BYTE(A, X),
|
||||
[=]() -> Pattern { return Word(0); },
|
||||
true,
|
||||
[=]() { return A.d() >= Pattern::WordSize / 8; }
|
||||
});
|
||||
|
||||
@ -320,13 +316,11 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart5(
|
||||
Word const mask = (Word(1) << 160) - 1;
|
||||
rules.push_back({
|
||||
Builtins::AND(Pattern{instr}, mask),
|
||||
[=]() -> Pattern { return {instr}; },
|
||||
false
|
||||
[=]() -> Pattern { return {instr}; }
|
||||
});
|
||||
rules.push_back({
|
||||
Builtins::AND(mask, Pattern{instr}),
|
||||
[=]() -> Pattern { return {instr}; },
|
||||
false
|
||||
[=]() -> Pattern { return {instr}; }
|
||||
});
|
||||
}
|
||||
|
||||
@ -357,21 +351,18 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart6(
|
||||
typename Builtins::PatternGeneratorInstance op{instr};
|
||||
rules.push_back({
|
||||
Builtins::ISZERO(Builtins::ISZERO(op(X, Y))),
|
||||
[=]() -> Pattern { return op(X, Y); },
|
||||
false
|
||||
[=]() -> Pattern { return op(X, Y); }
|
||||
});
|
||||
}
|
||||
|
||||
rules.push_back({
|
||||
Builtins::ISZERO(Builtins::ISZERO(Builtins::ISZERO(X))),
|
||||
[=]() -> Pattern { return Builtins::ISZERO(X); },
|
||||
false
|
||||
[=]() -> Pattern { return Builtins::ISZERO(X); }
|
||||
});
|
||||
|
||||
rules.push_back({
|
||||
Builtins::ISZERO(Builtins::XOR(X, Y)),
|
||||
[=]() -> Pattern { return Builtins::EQ(X, Y); },
|
||||
false
|
||||
[=]() -> Pattern { return Builtins::EQ(X, Y); }
|
||||
});
|
||||
|
||||
return rules;
|
||||
@ -409,23 +400,19 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
||||
rules += std::vector<SimplificationRule<Pattern>>{{
|
||||
// (X+A)+B -> X+(A+B)
|
||||
op(opXA, B),
|
||||
[=]() -> Pattern { return op(X, fun(A.d(), B.d())); },
|
||||
false
|
||||
[=]() -> Pattern { return op(X, fun(A.d(), B.d())); }
|
||||
}, {
|
||||
// (X+A)+Y -> (X+Y)+A
|
||||
op(opXA, Y),
|
||||
[=]() -> Pattern { return op(op(X, Y), A); },
|
||||
false
|
||||
[=]() -> Pattern { return op(op(X, Y), A); }
|
||||
}, {
|
||||
// B+(X+A) -> X+(A+B)
|
||||
op(B, opXA),
|
||||
[=]() -> Pattern { return op(X, fun(A.d(), B.d())); },
|
||||
false
|
||||
[=]() -> Pattern { return op(X, fun(A.d(), B.d())); }
|
||||
}, {
|
||||
// Y+(X+A) -> (Y+X)+A
|
||||
op(Y, opXA),
|
||||
[=]() -> Pattern { return op(op(Y, X), A); },
|
||||
false
|
||||
[=]() -> Pattern { return op(op(Y, X), A); }
|
||||
}};
|
||||
}
|
||||
}
|
||||
@ -440,8 +427,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
||||
return Builtins::AND(X, Word(0));
|
||||
else
|
||||
return Builtins::SHL(Word(sum), X);
|
||||
},
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
// Combine two SHR by constant
|
||||
@ -454,8 +440,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
||||
return Builtins::AND(X, Word(0));
|
||||
else
|
||||
return Builtins::SHR(Word(sum), X);
|
||||
},
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
// Combine SHL-SHR by constant
|
||||
@ -472,7 +457,6 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
||||
else
|
||||
return Builtins::AND(X, mask);
|
||||
},
|
||||
false,
|
||||
[=] { return A.d() < Pattern::WordSize && B.d() < Pattern::WordSize; }
|
||||
});
|
||||
|
||||
@ -490,7 +474,6 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
||||
else
|
||||
return Builtins::AND(X, mask);
|
||||
},
|
||||
false,
|
||||
[=] { return A.d() < Pattern::WordSize && B.d() < Pattern::WordSize; }
|
||||
});
|
||||
|
||||
@ -509,14 +492,12 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
||||
// SH[L/R](B, AND(X, A)) -> AND(SH[L/R](B, X), [ A << B / A >> B ])
|
||||
shiftOp(B, Builtins::AND(X, A)),
|
||||
replacement,
|
||||
false,
|
||||
[=] { return B.d() < Pattern::WordSize; }
|
||||
});
|
||||
rules.push_back({
|
||||
// SH[L/R](B, AND(A, X)) -> AND(SH[L/R](B, X), [ A << B / A >> B ])
|
||||
shiftOp(B, Builtins::AND(A, X)),
|
||||
replacement,
|
||||
false,
|
||||
[=] { return B.d() < Pattern::WordSize; }
|
||||
});
|
||||
}
|
||||
@ -526,17 +507,14 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
||||
Builtins::MUL(X, Builtins::SHL(Y, Word(1))),
|
||||
[=]() -> Pattern {
|
||||
return Builtins::SHL(Y, X);
|
||||
},
|
||||
// Actually only changes the order, does not remove.
|
||||
true
|
||||
}
|
||||
});
|
||||
rules.push_back({
|
||||
// MUL(SHL(X, 1), Y) -> SHL(X, Y)
|
||||
Builtins::MUL(Builtins::SHL(X, Word(1)), Y),
|
||||
[=]() -> Pattern {
|
||||
return Builtins::SHL(X, Y);
|
||||
},
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
rules.push_back({
|
||||
@ -544,9 +522,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
||||
Builtins::DIV(X, Builtins::SHL(Y, Word(1))),
|
||||
[=]() -> Pattern {
|
||||
return Builtins::SHR(Y, X);
|
||||
},
|
||||
// Actually only changes the order, does not remove.
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
std::function<bool()> feasibilityFunction = [=]() {
|
||||
@ -560,7 +536,6 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
||||
// AND(A, SHR(B, X)) -> A & ((2^256-1) >> B) == ((2^256-1) >> B)
|
||||
Builtins::AND(A, Builtins::SHR(B, X)),
|
||||
[=]() -> Pattern { return Builtins::SHR(B, X); },
|
||||
false,
|
||||
feasibilityFunction
|
||||
});
|
||||
|
||||
@ -568,28 +543,24 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
||||
// AND(SHR(B, X), A) -> ((2^256-1) >> B) & A == ((2^256-1) >> B)
|
||||
Builtins::AND(Builtins::SHR(B, X), A),
|
||||
[=]() -> Pattern { return Builtins::SHR(B, X); },
|
||||
false,
|
||||
feasibilityFunction
|
||||
});
|
||||
|
||||
rules.push_back({
|
||||
Builtins::BYTE(A, Builtins::SHL(B, X)),
|
||||
[=]() -> Pattern { return Builtins::BYTE(A.d() + B.d() / 8, X); },
|
||||
false,
|
||||
[=] { return B.d() % 8 == 0 && A.d() <= 32 && B.d() <= 256; }
|
||||
});
|
||||
|
||||
rules.push_back({
|
||||
Builtins::BYTE(A, Builtins::SHR(B, X)),
|
||||
[=]() -> Pattern { return Word(0); },
|
||||
true,
|
||||
[=] { return A.d() < B.d() / 8; }
|
||||
});
|
||||
|
||||
rules.push_back({
|
||||
Builtins::BYTE(A, Builtins::SHR(B, X)),
|
||||
[=]() -> Pattern { return Builtins::BYTE(A.d() - B.d() / 8, X); },
|
||||
false,
|
||||
[=] {
|
||||
return B.d() % 8 == 0 && A.d() < Pattern::WordSize / 8 && B.d() <= Pattern::WordSize && A.d() >= B.d() / 8;
|
||||
}
|
||||
@ -615,76 +586,28 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart8(
|
||||
{
|
||||
// X - A -> X + (-A)
|
||||
Builtins::SUB(X, A),
|
||||
[=]() -> Pattern { return Builtins::ADD(X, 0 - A.d()); },
|
||||
false
|
||||
[=]() -> Pattern { return Builtins::ADD(X, 0 - A.d()); }
|
||||
}, {
|
||||
// (X + A) - Y -> (X - Y) + A
|
||||
Builtins::SUB(Builtins::ADD(X, A), Y),
|
||||
[=]() -> Pattern { return Builtins::ADD(Builtins::SUB(X, Y), A); },
|
||||
false
|
||||
[=]() -> Pattern { return Builtins::ADD(Builtins::SUB(X, Y), A); }
|
||||
}, {
|
||||
// (A + X) - Y -> (X - Y) + A
|
||||
Builtins::SUB(Builtins::ADD(A, X), Y),
|
||||
[=]() -> Pattern { return Builtins::ADD(Builtins::SUB(X, Y), A); },
|
||||
false
|
||||
[=]() -> Pattern { return Builtins::ADD(Builtins::SUB(X, Y), A); }
|
||||
}, {
|
||||
// X - (Y + A) -> (X - Y) + (-A)
|
||||
Builtins::SUB(X, Builtins::ADD(Y, A)),
|
||||
[=]() -> Pattern { return Builtins::ADD(Builtins::SUB(X, Y), 0 - A.d()); },
|
||||
false
|
||||
[=]() -> Pattern { return Builtins::ADD(Builtins::SUB(X, Y), 0 - A.d()); }
|
||||
}, {
|
||||
// X - (A + Y) -> (X - Y) + (-A)
|
||||
Builtins::SUB(X, Builtins::ADD(A, Y)),
|
||||
[=]() -> Pattern { return Builtins::ADD(Builtins::SUB(X, Y), 0 - A.d()); },
|
||||
false
|
||||
[=]() -> Pattern { return Builtins::ADD(Builtins::SUB(X, Y), 0 - A.d()); }
|
||||
}
|
||||
};
|
||||
return rules;
|
||||
}
|
||||
|
||||
template <class Pattern>
|
||||
std::vector<SimplificationRule<Pattern>> simplificationRuleListPart9(
|
||||
Pattern,
|
||||
Pattern,
|
||||
Pattern,
|
||||
Pattern W,
|
||||
Pattern X,
|
||||
Pattern Y,
|
||||
Pattern Z
|
||||
)
|
||||
{
|
||||
using Word = typename Pattern::Word;
|
||||
using Builtins = typename Pattern::Builtins;
|
||||
std::vector<SimplificationRule<Pattern>> rules;
|
||||
|
||||
assertThrow(Pattern::WordSize > 160, OptimizerException, "");
|
||||
Word const mask = (Word(1) << 160) - 1;
|
||||
// CREATE
|
||||
rules.push_back({
|
||||
Builtins::AND(Builtins::CREATE(W, X, Y), mask),
|
||||
[=]() -> Pattern { return Builtins::CREATE(W, X, Y); },
|
||||
false
|
||||
});
|
||||
rules.push_back({
|
||||
Builtins::AND(mask, Builtins::CREATE(W, X, Y)),
|
||||
[=]() -> Pattern { return Builtins::CREATE(W, X, Y); },
|
||||
false
|
||||
});
|
||||
// CREATE2
|
||||
rules.push_back({
|
||||
Builtins::AND(Builtins::CREATE2(W, X, Y, Z), mask),
|
||||
[=]() -> Pattern { return Builtins::CREATE2(W, X, Y, Z); },
|
||||
false
|
||||
});
|
||||
rules.push_back({
|
||||
Builtins::AND(mask, Builtins::CREATE2(W, X, Y, Z)),
|
||||
[=]() -> Pattern { return Builtins::CREATE2(W, X, Y, Z); },
|
||||
false
|
||||
});
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
template<class Pattern>
|
||||
std::vector<SimplificationRule<Pattern>> evmRuleList(
|
||||
langutil::EVMVersion _evmVersion,
|
||||
@ -692,20 +615,35 @@ std::vector<SimplificationRule<Pattern>> evmRuleList(
|
||||
Pattern,
|
||||
Pattern,
|
||||
Pattern,
|
||||
Pattern,
|
||||
Pattern X,
|
||||
Pattern,
|
||||
Pattern
|
||||
)
|
||||
{
|
||||
using Builtins = typename Pattern::Builtins;
|
||||
using Word = typename Pattern::Word;
|
||||
std::vector<SimplificationRule<Pattern>> rules;
|
||||
|
||||
if (_evmVersion.hasSelfBalance())
|
||||
rules.push_back({
|
||||
Builtins::BALANCE(Instruction::ADDRESS),
|
||||
[]() -> Pattern { return Instruction::SELFBALANCE; }, false
|
||||
[]() -> Pattern { return Instruction::SELFBALANCE; }
|
||||
});
|
||||
|
||||
rules.emplace_back(
|
||||
Builtins::EXP(0, X),
|
||||
[=]() -> Pattern { return Builtins::ISZERO(X); }
|
||||
);
|
||||
rules.emplace_back(
|
||||
Builtins::EXP(1, X),
|
||||
[=]() -> Pattern { return Word(1); }
|
||||
);
|
||||
if (_evmVersion.hasBitwiseShifting())
|
||||
rules.emplace_back(
|
||||
Builtins::EXP(2, X),
|
||||
[=]() -> Pattern { return Builtins::SHL(X, 1); }
|
||||
);
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
@ -743,7 +681,6 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleList(
|
||||
rules += simplificationRuleListPart6(A, B, C, W, X);
|
||||
rules += simplificationRuleListPart7(A, B, C, W, X);
|
||||
rules += simplificationRuleListPart8(A, B, C, W, X);
|
||||
rules += simplificationRuleListPart9(A, B, C, W, X, Y, Z);
|
||||
|
||||
if (_evmVersion.has_value())
|
||||
rules += evmRuleList(*_evmVersion, A, B, C, W, X, Y, Z);
|
||||
|
@ -30,9 +30,8 @@ namespace solidity::evmasm
|
||||
|
||||
/**
|
||||
* Rule that contains a pattern, an action that can be applied
|
||||
* after the pattern has matched and a bool that indicates
|
||||
* whether the action would remove something from the expression
|
||||
* than is not a constant literal.
|
||||
* after the pattern has matched and optional condition to check if the
|
||||
* action should be applied.
|
||||
*/
|
||||
template <class Pattern>
|
||||
struct SimplificationRule
|
||||
@ -40,18 +39,15 @@ struct SimplificationRule
|
||||
SimplificationRule(
|
||||
Pattern _pattern,
|
||||
std::function<Pattern()> _action,
|
||||
bool _removesNonConstants,
|
||||
std::function<bool()> _feasible = {}
|
||||
):
|
||||
pattern(std::move(_pattern)),
|
||||
action(std::move(_action)),
|
||||
removesNonConstants(_removesNonConstants),
|
||||
feasible(std::move(_feasible))
|
||||
{}
|
||||
|
||||
Pattern pattern;
|
||||
std::function<Pattern()> action;
|
||||
bool removesNonConstants;
|
||||
std::function<bool()> feasible;
|
||||
};
|
||||
|
||||
|
@ -50,6 +50,7 @@ void CHCSmtLib2Interface::reset()
|
||||
{
|
||||
m_accumulatedOutput.clear();
|
||||
m_variables.clear();
|
||||
m_unhandledQueries.clear();
|
||||
}
|
||||
|
||||
void CHCSmtLib2Interface::registerRelation(Expression const& _expr)
|
||||
|
@ -9,6 +9,7 @@ set(sources
|
||||
SolverInterface.h
|
||||
Sorts.cpp
|
||||
Sorts.h
|
||||
Helpers.h
|
||||
)
|
||||
|
||||
if (${Z3_FOUND})
|
||||
|
@ -196,6 +196,12 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr)
|
||||
return m_context.mkExpr(CVC4::kind::BITVECTOR_OR, arguments[0], arguments[1]);
|
||||
else if (n == "bvxor")
|
||||
return m_context.mkExpr(CVC4::kind::BITVECTOR_XOR, arguments[0], arguments[1]);
|
||||
else if (n == "bvshl")
|
||||
return m_context.mkExpr(CVC4::kind::BITVECTOR_SHL, arguments[0], arguments[1]);
|
||||
else if (n == "bvlshr")
|
||||
return m_context.mkExpr(CVC4::kind::BITVECTOR_LSHR, arguments[0], arguments[1]);
|
||||
else if (n == "bvashr")
|
||||
return m_context.mkExpr(CVC4::kind::BITVECTOR_ASHR, arguments[0], arguments[1]);
|
||||
else if (n == "int2bv")
|
||||
{
|
||||
size_t size = std::stoul(_expr.arguments[1].name);
|
||||
@ -289,6 +295,8 @@ CVC4::Type CVC4Interface::cvc4Sort(Sort const& _sort)
|
||||
return m_context.booleanType();
|
||||
case Kind::Int:
|
||||
return m_context.integerType();
|
||||
case Kind::BitVector:
|
||||
return m_context.mkBitVectorType(dynamic_cast<BitVectorSort const&>(_sort).size);
|
||||
case Kind::Function:
|
||||
{
|
||||
FunctionSort const& fSort = dynamic_cast<FunctionSort const&>(_sort);
|
||||
|
58
libsmtutil/Helpers.h
Normal file
58
libsmtutil/Helpers.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsmtutil/SolverInterface.h>
|
||||
|
||||
namespace solidity::smtutil
|
||||
{
|
||||
|
||||
/// Signed division in SMTLIB2 rounds differently than EVM.
|
||||
/// This does not check for division by zero!
|
||||
inline Expression signedDivisionEVM(Expression _left, Expression _right)
|
||||
{
|
||||
return Expression::ite(
|
||||
_left >= 0,
|
||||
Expression::ite(_right >= 0, _left / _right, 0 - (_left / (0 - _right))),
|
||||
Expression::ite(_right >= 0, 0 - ((0 - _left) / _right), (0 - _left) / (0 - _right))
|
||||
);
|
||||
}
|
||||
|
||||
inline Expression abs(Expression _value)
|
||||
{
|
||||
return Expression::ite(_value >= 0, _value, 0 - _value);
|
||||
}
|
||||
|
||||
/// Signed modulo in SMTLIB2 behaves differently with regards
|
||||
/// to the sign than EVM.
|
||||
/// This does not check for modulo by zero!
|
||||
inline Expression signedModuloEVM(Expression _left, Expression _right)
|
||||
{
|
||||
return Expression::ite(
|
||||
_left >= 0,
|
||||
_left % _right,
|
||||
Expression::ite(
|
||||
(_left % _right) == 0,
|
||||
0,
|
||||
(_left % _right) - abs(_right)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -38,11 +38,11 @@ using namespace solidity::frontend;
|
||||
using namespace solidity::smtutil;
|
||||
|
||||
SMTLib2Interface::SMTLib2Interface(
|
||||
map<h256, string> const& _queryResponses,
|
||||
map<h256, string> _queryResponses,
|
||||
ReadCallback::Callback _smtCallback
|
||||
):
|
||||
m_queryResponses(_queryResponses),
|
||||
m_smtCallback(std::move(_smtCallback))
|
||||
m_queryResponses(move(_queryResponses)),
|
||||
m_smtCallback(move(_smtCallback))
|
||||
{
|
||||
reset();
|
||||
}
|
||||
@ -215,6 +215,8 @@ string SMTLib2Interface::toSmtLibSort(Sort const& _sort)
|
||||
return "Int";
|
||||
case Kind::Bool:
|
||||
return "Bool";
|
||||
case Kind::BitVector:
|
||||
return "(_ BitVec " + to_string(dynamic_cast<BitVectorSort const&>(_sort).size) + ")";
|
||||
case Kind::Array:
|
||||
{
|
||||
auto const& arraySort = dynamic_cast<ArraySort const&>(_sort);
|
||||
|
@ -39,8 +39,8 @@ class SMTLib2Interface: public SolverInterface, public boost::noncopyable
|
||||
{
|
||||
public:
|
||||
explicit SMTLib2Interface(
|
||||
std::map<util::h256, std::string> const& _queryResponses,
|
||||
frontend::ReadCallback::Callback _smtCallback
|
||||
std::map<util::h256, std::string> _queryResponses = {},
|
||||
frontend::ReadCallback::Callback _smtCallback = {}
|
||||
);
|
||||
|
||||
void reset() override;
|
||||
@ -77,7 +77,7 @@ private:
|
||||
std::map<std::string, SortPointer> m_variables;
|
||||
std::set<std::string> m_userSorts;
|
||||
|
||||
std::map<util::h256, std::string> const& m_queryResponses;
|
||||
std::map<util::h256, std::string> m_queryResponses;
|
||||
std::vector<std::string> m_unhandledQueries;
|
||||
|
||||
frontend::ReadCallback::Callback m_smtCallback;
|
||||
|
@ -33,12 +33,12 @@ using namespace solidity::frontend;
|
||||
using namespace solidity::smtutil;
|
||||
|
||||
SMTPortfolio::SMTPortfolio(
|
||||
map<h256, string> const& _smtlib2Responses,
|
||||
frontend::ReadCallback::Callback const& _smtCallback,
|
||||
map<h256, string> _smtlib2Responses,
|
||||
frontend::ReadCallback::Callback _smtCallback,
|
||||
[[maybe_unused]] SMTSolverChoice _enabledSolvers
|
||||
)
|
||||
{
|
||||
m_solvers.emplace_back(make_unique<SMTLib2Interface>(_smtlib2Responses, _smtCallback));
|
||||
m_solvers.emplace_back(make_unique<SMTLib2Interface>(move(_smtlib2Responses), move(_smtCallback)));
|
||||
#ifdef HAVE_Z3
|
||||
if (_enabledSolvers.z3)
|
||||
m_solvers.emplace_back(make_unique<Z3Interface>());
|
||||
|
@ -40,9 +40,9 @@ class SMTPortfolio: public SolverInterface, public boost::noncopyable
|
||||
{
|
||||
public:
|
||||
SMTPortfolio(
|
||||
std::map<util::h256, std::string> const& _smtlib2Responses,
|
||||
frontend::ReadCallback::Callback const& _smtCallback,
|
||||
SMTSolverChoice _enabledSolvers
|
||||
std::map<util::h256, std::string> _smtlib2Responses = {},
|
||||
frontend::ReadCallback::Callback _smtCallback = {},
|
||||
SMTSolverChoice _enabledSolvers = SMTSolverChoice::All()
|
||||
);
|
||||
|
||||
void reset() override;
|
||||
|
@ -99,6 +99,9 @@ public:
|
||||
{"bvand", 2},
|
||||
{"bvor", 2},
|
||||
{"bvxor", 2},
|
||||
{"bvshl", 2},
|
||||
{"bvlshr", 2},
|
||||
{"bvashr", 2},
|
||||
{"int2bv", 2},
|
||||
{"bv2int", 1},
|
||||
{"select", 2},
|
||||
@ -299,15 +302,30 @@ public:
|
||||
auto bvSort = _a.sort;
|
||||
return Expression("bvand", {std::move(_a), std::move(_b)}, bvSort);
|
||||
}
|
||||
friend Expression operator|(Expression _a, Expression _b)
|
||||
{
|
||||
auto bvSort = _a.sort;
|
||||
return Expression("bvor", {std::move(_a), std::move(_b)}, bvSort);
|
||||
}
|
||||
friend Expression operator^(Expression _a, Expression _b)
|
||||
{
|
||||
auto bvSort = _a.sort;
|
||||
return Expression("bvxor", {std::move(_a), std::move(_b)}, bvSort);
|
||||
}
|
||||
friend Expression operator|(Expression _a, Expression _b)
|
||||
friend Expression operator<<(Expression _a, Expression _b)
|
||||
{
|
||||
auto bvSort = _a.sort;
|
||||
return Expression("bvor", {std::move(_a), std::move(_b)}, bvSort);
|
||||
return Expression("bvshl", {std::move(_a), std::move(_b)}, bvSort);
|
||||
}
|
||||
friend Expression operator>>(Expression _a, Expression _b)
|
||||
{
|
||||
auto bvSort = _a.sort;
|
||||
return Expression("bvlshr", {std::move(_a), std::move(_b)}, bvSort);
|
||||
}
|
||||
static Expression ashr(Expression _a, Expression _b)
|
||||
{
|
||||
auto bvSort = _a.sort;
|
||||
return Expression("bvashr", {std::move(_a), std::move(_b)}, bvSort);
|
||||
}
|
||||
Expression operator()(std::vector<Expression> _arguments) const
|
||||
{
|
||||
|
@ -35,4 +35,6 @@ shared_ptr<IntSort> SortProvider::intSort(bool _signed)
|
||||
return uintSort;
|
||||
}
|
||||
|
||||
shared_ptr<BitVectorSort> const SortProvider::bitVectorSort{make_shared<BitVectorSort>(256)};
|
||||
|
||||
}
|
||||
|
@ -195,6 +195,7 @@ struct SortProvider
|
||||
static std::shared_ptr<IntSort> const uintSort;
|
||||
static std::shared_ptr<IntSort> const sintSort;
|
||||
static std::shared_ptr<IntSort> intSort(bool _signed = false);
|
||||
static std::shared_ptr<BitVectorSort> const bitVectorSort;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -95,9 +95,12 @@ pair<CheckResult, CHCSolverInterface::CexGraph> Z3CHCInterface::query(Expression
|
||||
}
|
||||
// TODO retrieve model / invariants
|
||||
}
|
||||
catch (z3::exception const&)
|
||||
catch (z3::exception const& _err)
|
||||
{
|
||||
result = CheckResult::ERROR;
|
||||
if (_err.msg() == string("max. resource limit exceeded"))
|
||||
result = CheckResult::UNKNOWN;
|
||||
else
|
||||
result = CheckResult::ERROR;
|
||||
cex = {};
|
||||
}
|
||||
|
||||
@ -142,14 +145,17 @@ instead of a path.
|
||||
*/
|
||||
CHCSolverInterface::CexGraph Z3CHCInterface::cexGraph(z3::expr const& _proof)
|
||||
{
|
||||
CexGraph graph;
|
||||
|
||||
/// The root fact of the refutation proof is `false`.
|
||||
/// The node itself is not a hyper resolution, so we need to
|
||||
/// extract the `query` hyper resolution node from the
|
||||
/// `false` node (the first child).
|
||||
smtAssert(_proof.is_app(), "");
|
||||
smtAssert(fact(_proof).decl().decl_kind() == Z3_OP_FALSE, "");
|
||||
/// The proof has the shape above for z3 >=4.8.8.
|
||||
/// If an older version is used, this check will fail and no
|
||||
/// counterexample will be generated.
|
||||
if (!_proof.is_app() || fact(_proof).decl().decl_kind() != Z3_OP_FALSE)
|
||||
return {};
|
||||
|
||||
CexGraph graph;
|
||||
|
||||
stack<z3::expr> proofStack;
|
||||
proofStack.push(_proof.arg(0));
|
||||
|
@ -189,6 +189,12 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
|
||||
return arguments[0] | arguments[1];
|
||||
else if (n == "bvxor")
|
||||
return arguments[0] ^ arguments[1];
|
||||
else if (n == "bvshl")
|
||||
return z3::shl(arguments[0], arguments[1]);
|
||||
else if (n == "bvlshr")
|
||||
return z3::lshr(arguments[0], arguments[1]);
|
||||
else if (n == "bvashr")
|
||||
return z3::ashr(arguments[0], arguments[1]);
|
||||
else if (n == "int2bv")
|
||||
{
|
||||
size_t size = std::stoul(_expr.arguments[1].name);
|
||||
@ -245,6 +251,8 @@ z3::sort Z3Interface::z3Sort(Sort const& _sort)
|
||||
return m_context.bool_sort();
|
||||
case Kind::Int:
|
||||
return m_context.int_sort();
|
||||
case Kind::BitVector:
|
||||
return m_context.bv_sort(dynamic_cast<BitVectorSort const&>(_sort).size);
|
||||
case Kind::Array:
|
||||
{
|
||||
auto const& arraySort = dynamic_cast<ArraySort const&>(_sort);
|
||||
|
@ -49,7 +49,7 @@ public:
|
||||
|
||||
// Z3 "basic resources" limit.
|
||||
// This is used to make the runs more deterministic and platform/machine independent.
|
||||
static int const resourceLimit = 12500000;
|
||||
static int const resourceLimit = 1000000;
|
||||
|
||||
private:
|
||||
void declareFunction(std::string const& _name, Sort const& _sort);
|
||||
|
@ -30,6 +30,8 @@ set(sources
|
||||
analysis/PostTypeChecker.h
|
||||
analysis/ReferencesResolver.cpp
|
||||
analysis/ReferencesResolver.h
|
||||
analysis/Scoper.cpp
|
||||
analysis/Scoper.h
|
||||
analysis/StaticAnalyzer.cpp
|
||||
analysis/StaticAnalyzer.h
|
||||
analysis/SyntaxChecker.cpp
|
||||
@ -102,6 +104,8 @@ set(sources
|
||||
formal/ModelChecker.h
|
||||
formal/Predicate.cpp
|
||||
formal/Predicate.h
|
||||
formal/PredicateSort.cpp
|
||||
formal/PredicateSort.h
|
||||
formal/SMTEncoder.cpp
|
||||
formal/SMTEncoder.h
|
||||
formal/SSAVariable.cpp
|
||||
|
@ -38,19 +38,51 @@ namespace
|
||||
{
|
||||
|
||||
template <class T, class B>
|
||||
bool hasEqualNameAndParameters(T const& _a, B const& _b)
|
||||
bool hasEqualParameters(T const& _a, B const& _b)
|
||||
{
|
||||
return
|
||||
_a.name() == _b.name() &&
|
||||
FunctionType(_a).asExternallyCallableFunction(false)->hasEqualParameterTypes(
|
||||
*FunctionType(_b).asExternallyCallableFunction(false)
|
||||
);
|
||||
return FunctionType(_a).asExternallyCallableFunction(false)->hasEqualParameterTypes(
|
||||
*FunctionType(_b).asExternallyCallableFunction(false)
|
||||
);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
map<ASTString, vector<T const*>> filterDeclarations(
|
||||
map<ASTString, vector<Declaration const*>> const& _declarations)
|
||||
{
|
||||
map<ASTString, vector<T const*>> filteredDeclarations;
|
||||
for (auto const& [name, overloads]: _declarations)
|
||||
for (auto const* declaration: overloads)
|
||||
if (auto typedDeclaration = dynamic_cast<T const*>(declaration))
|
||||
filteredDeclarations[name].push_back(typedDeclaration);
|
||||
return filteredDeclarations;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool ContractLevelChecker::check(SourceUnit const& _sourceUnit)
|
||||
{
|
||||
bool noErrors = true;
|
||||
findDuplicateDefinitions(
|
||||
filterDeclarations<FunctionDefinition>(*_sourceUnit.annotation().exportedSymbols)
|
||||
);
|
||||
// This check flags duplicate free events when free events become
|
||||
// a Solidity feature
|
||||
findDuplicateDefinitions(
|
||||
filterDeclarations<EventDefinition>(*_sourceUnit.annotation().exportedSymbols)
|
||||
);
|
||||
if (!Error::containsOnlyWarnings(m_errorReporter.errors()))
|
||||
noErrors = false;
|
||||
for (ASTPointer<ASTNode> const& node: _sourceUnit.nodes())
|
||||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
|
||||
if (!check(*contract))
|
||||
noErrors = false;
|
||||
return noErrors;
|
||||
}
|
||||
|
||||
bool ContractLevelChecker::check(ContractDefinition const& _contract)
|
||||
{
|
||||
_contract.annotation().unimplementedDeclarations = std::vector<Declaration const*>();
|
||||
|
||||
checkDuplicateFunctions(_contract);
|
||||
checkDuplicateEvents(_contract);
|
||||
m_overrideChecker.check(_contract);
|
||||
@ -141,8 +173,21 @@ void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const
|
||||
SecondarySourceLocation ssl;
|
||||
|
||||
for (size_t j = i + 1; j < overloads.size(); ++j)
|
||||
if (hasEqualNameAndParameters(*overloads[i], *overloads[j]))
|
||||
if (hasEqualParameters(*overloads[i], *overloads[j]))
|
||||
{
|
||||
solAssert(
|
||||
(
|
||||
dynamic_cast<ContractDefinition const*>(overloads[i]->scope()) &&
|
||||
dynamic_cast<ContractDefinition const*>(overloads[j]->scope()) &&
|
||||
overloads[i]->name() == overloads[j]->name()
|
||||
) ||
|
||||
(
|
||||
dynamic_cast<SourceUnit const*>(overloads[i]->scope()) &&
|
||||
dynamic_cast<SourceUnit const*>(overloads[j]->scope())
|
||||
),
|
||||
"Override is neither a namesake function/event in contract scope nor "
|
||||
"a free function/event (alias)."
|
||||
);
|
||||
ssl.append("Other declaration is here:", overloads[j]->location());
|
||||
reported.insert(j);
|
||||
}
|
||||
@ -210,9 +255,10 @@ void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _c
|
||||
// Set to not fully implemented if at least one flag is false.
|
||||
// Note that `_contract.annotation().unimplementedDeclarations` has already been
|
||||
// pre-filled by `checkBaseConstructorArguments`.
|
||||
//
|
||||
for (auto const& proxy: proxies)
|
||||
if (proxy.unimplemented())
|
||||
_contract.annotation().unimplementedDeclarations.push_back(proxy.declaration());
|
||||
_contract.annotation().unimplementedDeclarations->push_back(proxy.declaration());
|
||||
|
||||
if (_contract.abstract())
|
||||
{
|
||||
@ -229,17 +275,17 @@ void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _c
|
||||
if (
|
||||
_contract.contractKind() == ContractKind::Contract &&
|
||||
!_contract.abstract() &&
|
||||
!_contract.annotation().unimplementedDeclarations.empty()
|
||||
!_contract.annotation().unimplementedDeclarations->empty()
|
||||
)
|
||||
{
|
||||
SecondarySourceLocation ssl;
|
||||
for (auto declaration: _contract.annotation().unimplementedDeclarations)
|
||||
for (auto declaration: *_contract.annotation().unimplementedDeclarations)
|
||||
ssl.append("Missing implementation: ", declaration->location());
|
||||
m_errorReporter.typeError(
|
||||
3656_error,
|
||||
_contract.location(),
|
||||
ssl,
|
||||
"Contract \"" + _contract.annotation().canonicalName + "\" should be marked as abstract."
|
||||
"Contract \"" + *_contract.annotation().canonicalName + "\" should be marked as abstract."
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -289,7 +335,7 @@ void ContractLevelChecker::checkBaseConstructorArguments(ContractDefinition cons
|
||||
if (FunctionDefinition const* constructor = contract->constructor())
|
||||
if (contract != &_contract && !constructor->parameters().empty())
|
||||
if (!_contract.annotation().baseConstructorArguments.count(constructor))
|
||||
_contract.annotation().unimplementedDeclarations.push_back(constructor);
|
||||
_contract.annotation().unimplementedDeclarations->push_back(constructor);
|
||||
}
|
||||
|
||||
void ContractLevelChecker::annotateBaseConstructorArguments(
|
||||
@ -465,7 +511,6 @@ void ContractLevelChecker::checkPayableFallbackWithoutReceive(ContractDefinition
|
||||
void ContractLevelChecker::checkStorageSize(ContractDefinition const& _contract)
|
||||
{
|
||||
bigint size = 0;
|
||||
vector<VariableDeclaration const*> variables;
|
||||
for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts))
|
||||
for (VariableDeclaration const* variable: contract->stateVariables())
|
||||
if (!(variable->isConstant() || variable->immutable()))
|
||||
@ -473,7 +518,7 @@ void ContractLevelChecker::checkStorageSize(ContractDefinition const& _contract)
|
||||
size += variable->annotation().type->storageSizeUpperBound();
|
||||
if (size >= bigint(1) << 256)
|
||||
{
|
||||
m_errorReporter.typeError(7676_error, _contract.location(), "Contract too large for storage.");
|
||||
m_errorReporter.typeError(7676_error, _contract.location(), "Contract requires too much storage.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ namespace solidity::frontend
|
||||
|
||||
/**
|
||||
* Component that verifies overloads, abstract contracts, function clashes and others
|
||||
* checks at contract or function level.
|
||||
* checks at file, contract, or function level.
|
||||
*/
|
||||
class ContractLevelChecker
|
||||
{
|
||||
@ -51,11 +51,14 @@ public:
|
||||
m_errorReporter(_errorReporter)
|
||||
{}
|
||||
|
||||
/// Performs checks on the given source ast.
|
||||
/// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings
|
||||
bool check(SourceUnit const& _sourceUnit);
|
||||
|
||||
private:
|
||||
/// Performs checks on the given contract.
|
||||
/// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings
|
||||
bool check(ContractDefinition const& _contract);
|
||||
|
||||
private:
|
||||
/// Checks that two functions defined in this contract with the same name have different
|
||||
/// arguments and that there is at most one constructor.
|
||||
void checkDuplicateFunctions(ContractDefinition const& _contract);
|
||||
|
@ -167,7 +167,7 @@ void ImmutableValidator::analyseVariableReference(VariableDeclaration const& _va
|
||||
|
||||
// If this is not an ordinary assignment, we write and read at the same time.
|
||||
bool write = _expression.annotation().willBeWrittenTo;
|
||||
bool read = !_expression.annotation().willBeWrittenTo || !_expression.annotation().lValueOfOrdinaryAssignment;
|
||||
bool read = !_expression.annotation().willBeWrittenTo || !*_expression.annotation().lValueOfOrdinaryAssignment;
|
||||
if (write)
|
||||
{
|
||||
if (!m_currentConstructor)
|
||||
|
@ -74,7 +74,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
|
||||
for (auto const& node: _sourceUnit.nodes())
|
||||
if (auto imp = dynamic_cast<ImportDirective const*>(node.get()))
|
||||
{
|
||||
string const& path = imp->annotation().absolutePath;
|
||||
string const& path = *imp->annotation().absolutePath;
|
||||
if (!_sourceUnits.count(path))
|
||||
{
|
||||
m_errorReporter.declarationError(
|
||||
@ -121,6 +121,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
|
||||
))
|
||||
error = true;
|
||||
}
|
||||
_sourceUnit.annotation().exportedSymbols = m_scopes[&_sourceUnit]->declarations();
|
||||
return !error;
|
||||
}
|
||||
|
||||
@ -503,11 +504,20 @@ bool DeclarationRegistrationHelper::registerDeclaration(
|
||||
else
|
||||
{
|
||||
auto shadowedLocation = shadowedDeclaration->location();
|
||||
_errorReporter.warning(
|
||||
2519_error,
|
||||
_declaration.location(),
|
||||
"This declaration shadows an existing declaration.",
|
||||
SecondarySourceLocation().append("The shadowed declaration is here:", shadowedLocation)
|
||||
|
||||
if (!shadowedDeclaration->isVisibleInContract())
|
||||
_errorReporter.warning(
|
||||
8760_error,
|
||||
_declaration.location(),
|
||||
"This declaration has the same name as another declaration.",
|
||||
SecondarySourceLocation().append("The other declaration is here:", shadowedLocation)
|
||||
);
|
||||
else
|
||||
_errorReporter.warning(
|
||||
2519_error,
|
||||
_declaration.location(),
|
||||
"This declaration shadows an existing declaration.",
|
||||
SecondarySourceLocation().append("The shadowed declaration is here:", shadowedLocation)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -519,14 +529,12 @@ bool DeclarationRegistrationHelper::visit(SourceUnit& _sourceUnit)
|
||||
if (!m_scopes[&_sourceUnit])
|
||||
// By importing, it is possible that the container already exists.
|
||||
m_scopes[&_sourceUnit] = make_shared<DeclarationContainer>(m_currentScope, m_scopes[m_currentScope].get());
|
||||
m_currentScope = &_sourceUnit;
|
||||
return true;
|
||||
return ASTVisitor::visit(_sourceUnit);
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::endVisit(SourceUnit& _sourceUnit)
|
||||
{
|
||||
_sourceUnit.annotation().exportedSymbols = m_scopes[&_sourceUnit]->declarations();
|
||||
closeCurrentScope();
|
||||
ASTVisitor::endVisit(_sourceUnit);
|
||||
}
|
||||
|
||||
bool DeclarationRegistrationHelper::visit(ImportDirective& _import)
|
||||
@ -536,8 +544,7 @@ bool DeclarationRegistrationHelper::visit(ImportDirective& _import)
|
||||
if (!m_scopes[importee])
|
||||
m_scopes[importee] = make_shared<DeclarationContainer>(nullptr, m_scopes[nullptr].get());
|
||||
m_scopes[&_import] = m_scopes[importee];
|
||||
registerDeclaration(_import, false);
|
||||
return true;
|
||||
return ASTVisitor::visit(_import);
|
||||
}
|
||||
|
||||
bool DeclarationRegistrationHelper::visit(ContractDefinition& _contract)
|
||||
@ -547,122 +554,17 @@ bool DeclarationRegistrationHelper::visit(ContractDefinition& _contract)
|
||||
m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentSuper(), nullptr, false, true);
|
||||
m_currentContract = &_contract;
|
||||
|
||||
registerDeclaration(_contract, true);
|
||||
_contract.annotation().canonicalName = currentCanonicalName();
|
||||
return true;
|
||||
return ASTVisitor::visit(_contract);
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::endVisit(ContractDefinition&)
|
||||
void DeclarationRegistrationHelper::endVisit(ContractDefinition& _contract)
|
||||
{
|
||||
// make "this" and "super" invisible.
|
||||
m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentThis(), nullptr, true, true);
|
||||
m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentSuper(), nullptr, true, true);
|
||||
m_globalContext.resetCurrentContract();
|
||||
m_currentContract = nullptr;
|
||||
closeCurrentScope();
|
||||
}
|
||||
|
||||
bool DeclarationRegistrationHelper::visit(StructDefinition& _struct)
|
||||
{
|
||||
registerDeclaration(_struct, true);
|
||||
_struct.annotation().canonicalName = currentCanonicalName();
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::endVisit(StructDefinition&)
|
||||
{
|
||||
closeCurrentScope();
|
||||
}
|
||||
|
||||
bool DeclarationRegistrationHelper::visit(EnumDefinition& _enum)
|
||||
{
|
||||
registerDeclaration(_enum, true);
|
||||
_enum.annotation().canonicalName = currentCanonicalName();
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::endVisit(EnumDefinition&)
|
||||
{
|
||||
closeCurrentScope();
|
||||
}
|
||||
|
||||
bool DeclarationRegistrationHelper::visit(EnumValue& _value)
|
||||
{
|
||||
registerDeclaration(_value, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeclarationRegistrationHelper::visit(FunctionDefinition& _function)
|
||||
{
|
||||
registerDeclaration(_function, true);
|
||||
m_currentFunction = &_function;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::endVisit(FunctionDefinition&)
|
||||
{
|
||||
m_currentFunction = nullptr;
|
||||
closeCurrentScope();
|
||||
}
|
||||
|
||||
bool DeclarationRegistrationHelper::visit(TryCatchClause& _tryCatchClause)
|
||||
{
|
||||
_tryCatchClause.annotation().scope = m_currentScope;
|
||||
enterNewSubScope(_tryCatchClause);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::endVisit(TryCatchClause&)
|
||||
{
|
||||
closeCurrentScope();
|
||||
}
|
||||
|
||||
bool DeclarationRegistrationHelper::visit(ModifierDefinition& _modifier)
|
||||
{
|
||||
registerDeclaration(_modifier, true);
|
||||
m_currentFunction = &_modifier;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::endVisit(ModifierDefinition&)
|
||||
{
|
||||
m_currentFunction = nullptr;
|
||||
closeCurrentScope();
|
||||
}
|
||||
|
||||
bool DeclarationRegistrationHelper::visit(FunctionTypeName& _funTypeName)
|
||||
{
|
||||
enterNewSubScope(_funTypeName);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::endVisit(FunctionTypeName&)
|
||||
{
|
||||
closeCurrentScope();
|
||||
}
|
||||
|
||||
bool DeclarationRegistrationHelper::visit(Block& _block)
|
||||
{
|
||||
_block.annotation().scope = m_currentScope;
|
||||
enterNewSubScope(_block);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::endVisit(Block&)
|
||||
{
|
||||
closeCurrentScope();
|
||||
}
|
||||
|
||||
bool DeclarationRegistrationHelper::visit(ForStatement& _for)
|
||||
{
|
||||
_for.annotation().scope = m_currentScope;
|
||||
enterNewSubScope(_for);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::endVisit(ForStatement&)
|
||||
{
|
||||
closeCurrentScope();
|
||||
ASTVisitor::endVisit(_contract);
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::endVisit(VariableDeclarationStatement& _variableDeclarationStatement)
|
||||
@ -673,32 +575,42 @@ void DeclarationRegistrationHelper::endVisit(VariableDeclarationStatement& _vari
|
||||
for (ASTPointer<VariableDeclaration> const& var: _variableDeclarationStatement.declarations())
|
||||
if (var)
|
||||
m_currentFunction->addLocalVariable(*var);
|
||||
ASTVisitor::endVisit(_variableDeclarationStatement);
|
||||
}
|
||||
|
||||
bool DeclarationRegistrationHelper::visit(VariableDeclaration& _declaration)
|
||||
bool DeclarationRegistrationHelper::visitNode(ASTNode& _node)
|
||||
{
|
||||
registerDeclaration(_declaration, false);
|
||||
if (auto const* scopable = dynamic_cast<Scopable const*>(&_node))
|
||||
solAssert(scopable->annotation().scope == m_currentScope, "");
|
||||
|
||||
if (auto* declaration = dynamic_cast<Declaration*>(&_node))
|
||||
registerDeclaration(*declaration);
|
||||
if (dynamic_cast<ScopeOpener const*>(&_node))
|
||||
enterNewSubScope(_node);
|
||||
|
||||
if (auto* variableScope = dynamic_cast<VariableScope*>(&_node))
|
||||
m_currentFunction = variableScope;
|
||||
if (auto* annotation = dynamic_cast<TypeDeclarationAnnotation*>(&_node.annotation()))
|
||||
annotation->canonicalName = currentCanonicalName();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeclarationRegistrationHelper::visit(EventDefinition& _event)
|
||||
void DeclarationRegistrationHelper::endVisitNode(ASTNode& _node)
|
||||
{
|
||||
registerDeclaration(_event, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::endVisit(EventDefinition&)
|
||||
{
|
||||
closeCurrentScope();
|
||||
if (dynamic_cast<ScopeOpener const*>(&_node))
|
||||
closeCurrentScope();
|
||||
if (dynamic_cast<VariableScope*>(&_node))
|
||||
m_currentFunction = nullptr;
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::enterNewSubScope(ASTNode& _subScope)
|
||||
{
|
||||
map<ASTNode const*, shared_ptr<DeclarationContainer>>::iterator iter;
|
||||
bool newlyAdded;
|
||||
shared_ptr<DeclarationContainer> container{make_shared<DeclarationContainer>(m_currentScope, m_scopes[m_currentScope].get())};
|
||||
tie(iter, newlyAdded) = m_scopes.emplace(&_subScope, move(container));
|
||||
solAssert(newlyAdded, "Unable to add new scope.");
|
||||
bool newlyAdded = m_scopes.emplace(&_subScope, move(container)).second;
|
||||
// Source units are the only AST nodes for which containers can be created from multiple places
|
||||
// due to imports.
|
||||
solAssert(newlyAdded || dynamic_cast<SourceUnit const*>(&_subScope), "Unable to add new scope.");
|
||||
m_currentScope = &_subScope;
|
||||
}
|
||||
|
||||
@ -708,7 +620,7 @@ void DeclarationRegistrationHelper::closeCurrentScope()
|
||||
m_currentScope = m_scopes[m_currentScope]->enclosingNode();
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration, bool _opensScope)
|
||||
void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration)
|
||||
{
|
||||
solAssert(m_currentScope && m_scopes.count(m_currentScope), "No current scope.");
|
||||
|
||||
@ -729,10 +641,8 @@ void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaratio
|
||||
|
||||
registerDeclaration(*m_scopes[m_currentScope], _declaration, nullptr, nullptr, warnAboutShadowing, inactive, m_errorReporter);
|
||||
|
||||
_declaration.annotation().scope = m_currentScope;
|
||||
_declaration.annotation().contract = m_currentContract;
|
||||
if (_opensScope)
|
||||
enterNewSubScope(_declaration);
|
||||
solAssert(_declaration.annotation().scope == m_currentScope, "");
|
||||
solAssert(_declaration.annotation().contract == m_currentContract, "");
|
||||
}
|
||||
|
||||
string DeclarationRegistrationHelper::currentCanonicalName() const
|
||||
|
@ -163,31 +163,15 @@ private:
|
||||
bool visit(ImportDirective& _import) override;
|
||||
bool visit(ContractDefinition& _contract) override;
|
||||
void endVisit(ContractDefinition& _contract) override;
|
||||
bool visit(StructDefinition& _struct) override;
|
||||
void endVisit(StructDefinition& _struct) override;
|
||||
bool visit(EnumDefinition& _enum) override;
|
||||
void endVisit(EnumDefinition& _enum) override;
|
||||
bool visit(EnumValue& _value) override;
|
||||
bool visit(FunctionDefinition& _function) override;
|
||||
void endVisit(FunctionDefinition& _function) override;
|
||||
bool visit(TryCatchClause& _tryCatchClause) override;
|
||||
void endVisit(TryCatchClause& _tryCatchClause) override;
|
||||
bool visit(ModifierDefinition& _modifier) override;
|
||||
void endVisit(ModifierDefinition& _modifier) override;
|
||||
bool visit(FunctionTypeName& _funTypeName) override;
|
||||
void endVisit(FunctionTypeName& _funTypeName) override;
|
||||
bool visit(Block& _block) override;
|
||||
void endVisit(Block& _block) override;
|
||||
bool visit(ForStatement& _forLoop) override;
|
||||
void endVisit(ForStatement& _forLoop) override;
|
||||
void endVisit(VariableDeclarationStatement& _variableDeclarationStatement) override;
|
||||
bool visit(VariableDeclaration& _declaration) override;
|
||||
bool visit(EventDefinition& _event) override;
|
||||
void endVisit(EventDefinition& _event) override;
|
||||
|
||||
bool visitNode(ASTNode& _node) override;
|
||||
void endVisitNode(ASTNode& _node) override;
|
||||
|
||||
|
||||
void enterNewSubScope(ASTNode& _subScope);
|
||||
void closeCurrentScope();
|
||||
void registerDeclaration(Declaration& _declaration, bool _opensScope);
|
||||
void registerDeclaration(Declaration& _declaration);
|
||||
|
||||
static bool isOverloadedFunction(Declaration const& _declaration1, Declaration const& _declaration2);
|
||||
|
||||
|
63
libsolidity/analysis/Scoper.cpp
Normal file
63
libsolidity/analysis/Scoper.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#include <libsolidity/analysis/Scoper.h>
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::frontend;
|
||||
|
||||
void Scoper::assignScopes(ASTNode const& _astRoot)
|
||||
{
|
||||
Scoper scoper;
|
||||
_astRoot.accept(scoper);
|
||||
}
|
||||
|
||||
bool Scoper::visit(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(m_contract == nullptr, "");
|
||||
m_contract = &_contract;
|
||||
return ASTConstVisitor::visit(_contract);
|
||||
}
|
||||
|
||||
void Scoper::endVisit(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(m_contract == &_contract, "");
|
||||
m_contract = nullptr;
|
||||
ASTConstVisitor::endVisit(_contract);
|
||||
}
|
||||
|
||||
bool Scoper::visitNode(ASTNode const& _node)
|
||||
{
|
||||
if (auto const* scopable = dynamic_cast<Scopable const*>(&_node))
|
||||
{
|
||||
scopable->annotation().scope = m_scopes.empty() ? nullptr : m_scopes.back();
|
||||
scopable->annotation().contract = m_contract;
|
||||
}
|
||||
if (dynamic_cast<ScopeOpener const*>(&_node))
|
||||
m_scopes.push_back(&_node);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Scoper::endVisitNode(ASTNode const& _node)
|
||||
{
|
||||
if (dynamic_cast<ScopeOpener const*>(&_node))
|
||||
m_scopes.pop_back();
|
||||
}
|
@ -18,31 +18,28 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <test/libsolidity/SyntaxTest.h>
|
||||
#include <libsolidity/ast/ASTForward.h>
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
|
||||
#include <libsolutil/JSON.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace solidity::frontend::test
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
class SMTCheckerJSONTest: public SyntaxTest
|
||||
/**
|
||||
* AST visitor that assigns syntactic scopes.
|
||||
*/
|
||||
class Scoper: private ASTConstVisitor
|
||||
{
|
||||
public:
|
||||
static std::unique_ptr<TestCase> create(Config const& _config)
|
||||
{
|
||||
return std::make_unique<SMTCheckerJSONTest>(_config.filename, _config.evmVersion);
|
||||
}
|
||||
SMTCheckerJSONTest(std::string const& _filename, langutil::EVMVersion _evmVersion);
|
||||
|
||||
TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override;
|
||||
static void assignScopes(ASTNode const& _astRoot);
|
||||
|
||||
private:
|
||||
std::vector<std::string> hashesFromJson(Json::Value const& _jsonObj, std::string const& _auxInput, std::string const& _smtlib);
|
||||
Json::Value buildJson(std::string const& _extra);
|
||||
bool visit(ContractDefinition const& _contract) override;
|
||||
void endVisit(ContractDefinition const& _contract) override;
|
||||
bool visitNode(ASTNode const& _node) override;
|
||||
void endVisitNode(ASTNode const& _node) override;
|
||||
|
||||
Json::Value m_smtResponses;
|
||||
ContractDefinition const* m_contract = nullptr;
|
||||
std::vector<ASTNode const*> m_scopes;
|
||||
};
|
||||
|
||||
}
|
@ -156,6 +156,19 @@ bool StaticAnalyzer::visit(VariableDeclaration const& _variable)
|
||||
// This is not a no-op, the entry might pre-exist.
|
||||
m_localVarUseCount[make_pair(_variable.id(), &_variable)] += 0;
|
||||
}
|
||||
|
||||
if (_variable.isStateVariable() || _variable.referenceLocation() == VariableDeclaration::Location::Storage)
|
||||
if (auto varType = dynamic_cast<CompositeType const*>(_variable.annotation().type))
|
||||
for (Type const* type: varType->fullDecomposition())
|
||||
if (type->storageSizeUpperBound() >= (bigint(1) << 64))
|
||||
{
|
||||
string message = "Type " + type->toString(true) +
|
||||
" covers a large part of storage and thus makes collisions likely."
|
||||
" Either use mappings or dynamic arrays and allow their size to be increased only"
|
||||
" in small quantities per transaction.";
|
||||
m_errorReporter.warning(7325_error, _variable.typeName().location(), message);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -172,7 +185,7 @@ bool StaticAnalyzer::visit(Return const& _return)
|
||||
|
||||
bool StaticAnalyzer::visit(ExpressionStatement const& _statement)
|
||||
{
|
||||
if (_statement.expression().annotation().isPure)
|
||||
if (*_statement.expression().annotation().isPure)
|
||||
m_errorReporter.warning(
|
||||
6133_error,
|
||||
_statement.location(),
|
||||
@ -274,7 +287,7 @@ bool StaticAnalyzer::visit(InlineAssembly const& _inlineAssembly)
|
||||
bool StaticAnalyzer::visit(BinaryOperation const& _operation)
|
||||
{
|
||||
if (
|
||||
_operation.rightExpression().annotation().isPure &&
|
||||
*_operation.rightExpression().annotation().isPure &&
|
||||
(_operation.getOperator() == Token::Div || _operation.getOperator() == Token::Mod)
|
||||
)
|
||||
if (auto rhs = dynamic_cast<RationalNumberType const*>(
|
||||
@ -299,7 +312,7 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall)
|
||||
if (functionType->kind() == FunctionType::Kind::AddMod || functionType->kind() == FunctionType::Kind::MulMod)
|
||||
{
|
||||
solAssert(_functionCall.arguments().size() == 3, "");
|
||||
if (_functionCall.arguments()[2]->annotation().isPure)
|
||||
if (*_functionCall.arguments()[2]->annotation().isPure)
|
||||
if (auto lastArg = dynamic_cast<RationalNumberType const*>(
|
||||
ConstantEvaluator(m_errorReporter).evaluate(*(_functionCall.arguments())[2])
|
||||
))
|
||||
|
@ -219,11 +219,12 @@ bool SyntaxChecker::visit(Throw const& _throwStatement)
|
||||
|
||||
bool SyntaxChecker::visit(Literal const& _literal)
|
||||
{
|
||||
if ((_literal.token() == Token::UnicodeStringLiteral) && !validateUTF8(_literal.value()))
|
||||
size_t invalidSequence;
|
||||
if ((_literal.token() == Token::UnicodeStringLiteral) && !validateUTF8(_literal.value(), invalidSequence))
|
||||
m_errorReporter.syntaxError(
|
||||
8452_error,
|
||||
_literal.location(),
|
||||
"Invalid UTF-8 sequence found"
|
||||
"Contains invalid UTF-8 sequence at position " + toString(invalidSequence) + "."
|
||||
);
|
||||
|
||||
if (_literal.token() != Token::Number)
|
||||
|
@ -320,6 +320,15 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
|
||||
|
||||
void TypeChecker::endVisit(ModifierDefinition const& _modifier)
|
||||
{
|
||||
if (_modifier.virtualSemantics())
|
||||
if (auto const* contractDef = dynamic_cast<ContractDefinition const*>(_modifier.scope()))
|
||||
if (contractDef->isLibrary())
|
||||
m_errorReporter.typeError(
|
||||
3275_error,
|
||||
_modifier.location(),
|
||||
"Modifiers in a library cannot be virtual."
|
||||
);
|
||||
|
||||
if (!_modifier.isImplemented() && !_modifier.virtualSemantics())
|
||||
m_errorReporter.typeError(8063_error, _modifier.location(), "Modifiers without implementation must be marked virtual.");
|
||||
}
|
||||
@ -529,7 +538,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
|
||||
|
||||
if (!_variable.value())
|
||||
m_errorReporter.typeError(4266_error, _variable.location(), "Uninitialized \"constant\" variable.");
|
||||
else if (!_variable.value()->annotation().isPure)
|
||||
else if (!*_variable.value()->annotation().isPure)
|
||||
m_errorReporter.typeError(
|
||||
8349_error,
|
||||
_variable.value()->location(),
|
||||
@ -600,29 +609,6 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
|
||||
}
|
||||
}
|
||||
|
||||
if (varType->dataStoredIn(DataLocation::Storage))
|
||||
{
|
||||
auto collisionMessage = [&](string const& variableOrType, bool isVariable) -> string {
|
||||
return
|
||||
(isVariable ? "Variable " : "Type ") +
|
||||
util::escapeAndQuoteString(variableOrType) +
|
||||
" covers a large part of storage and thus makes collisions likely."
|
||||
" Either use mappings or dynamic arrays and allow their size to be increased only"
|
||||
" in small quantities per transaction.";
|
||||
};
|
||||
|
||||
if (varType->storageSizeUpperBound() >= bigint(1) << 64)
|
||||
{
|
||||
if (_variable.isStateVariable())
|
||||
m_errorReporter.warning(3408_error, _variable.location(), collisionMessage(_variable.name(), true));
|
||||
else
|
||||
m_errorReporter.warning(2332_error, _variable.typeName().location(), collisionMessage(varType->toString(true), false));
|
||||
}
|
||||
vector<Type const*> oversizedSubtypes = frontend::oversizedSubtypes(*varType);
|
||||
for (Type const* subtype: oversizedSubtypes)
|
||||
m_errorReporter.warning(7325_error, _variable.typeName().location(), collisionMessage(subtype->canonicalName(), false));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1310,11 +1296,14 @@ bool TypeChecker::visit(Conditional const& _conditional)
|
||||
}
|
||||
}
|
||||
|
||||
_conditional.annotation().isConstant = false;
|
||||
_conditional.annotation().type = commonType;
|
||||
_conditional.annotation().isPure =
|
||||
_conditional.condition().annotation().isPure &&
|
||||
_conditional.trueExpression().annotation().isPure &&
|
||||
_conditional.falseExpression().annotation().isPure;
|
||||
*_conditional.condition().annotation().isPure &&
|
||||
*_conditional.trueExpression().annotation().isPure &&
|
||||
*_conditional.falseExpression().annotation().isPure;
|
||||
|
||||
_conditional.annotation().isLValue = false;
|
||||
|
||||
if (_conditional.annotation().willBeWrittenTo)
|
||||
m_errorReporter.typeError(
|
||||
@ -1368,6 +1357,9 @@ bool TypeChecker::visit(Assignment const& _assignment)
|
||||
);
|
||||
TypePointer t = type(_assignment.leftHandSide());
|
||||
_assignment.annotation().type = t;
|
||||
_assignment.annotation().isPure = false;
|
||||
_assignment.annotation().isLValue = false;
|
||||
_assignment.annotation().isConstant = false;
|
||||
|
||||
checkExpressionAssignment(*t, _assignment.leftHandSide());
|
||||
|
||||
@ -1415,6 +1407,7 @@ bool TypeChecker::visit(Assignment const& _assignment)
|
||||
|
||||
bool TypeChecker::visit(TupleExpression const& _tuple)
|
||||
{
|
||||
_tuple.annotation().isConstant = false;
|
||||
vector<ASTPointer<Expression>> const& components = _tuple.components();
|
||||
TypePointers types;
|
||||
|
||||
@ -1427,7 +1420,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
|
||||
{
|
||||
requireLValue(
|
||||
*component,
|
||||
_tuple.annotation().lValueOfOrdinaryAssignment
|
||||
*_tuple.annotation().lValueOfOrdinaryAssignment
|
||||
);
|
||||
types.push_back(type(*component));
|
||||
}
|
||||
@ -1439,6 +1432,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
|
||||
_tuple.annotation().type = TypeProvider::tuple(move(types));
|
||||
// If some of the components are not LValues, the error is reported above.
|
||||
_tuple.annotation().isLValue = true;
|
||||
_tuple.annotation().isPure = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1478,7 +1472,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
|
||||
else if (inlineArrayType)
|
||||
inlineArrayType = Type::commonType(inlineArrayType, types[i]);
|
||||
}
|
||||
if (!components[i]->annotation().isPure)
|
||||
if (!*components[i]->annotation().isPure)
|
||||
isPure = false;
|
||||
}
|
||||
_tuple.annotation().isPure = isPure;
|
||||
@ -1509,6 +1503,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
|
||||
_tuple.annotation().type = TypeProvider::tuple(move(types));
|
||||
}
|
||||
|
||||
_tuple.annotation().isLValue = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -1536,7 +1531,9 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
|
||||
t = subExprType;
|
||||
}
|
||||
_operation.annotation().type = t;
|
||||
_operation.annotation().isPure = !modifying && _operation.subExpression().annotation().isPure;
|
||||
_operation.annotation().isConstant = false;
|
||||
_operation.annotation().isPure = !modifying && *_operation.subExpression().annotation().isPure;
|
||||
_operation.annotation().isLValue = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1567,8 +1564,10 @@ void TypeChecker::endVisit(BinaryOperation const& _operation)
|
||||
TypeProvider::boolean() :
|
||||
commonType;
|
||||
_operation.annotation().isPure =
|
||||
_operation.leftExpression().annotation().isPure &&
|
||||
_operation.rightExpression().annotation().isPure;
|
||||
*_operation.leftExpression().annotation().isPure &&
|
||||
*_operation.rightExpression().annotation().isPure;
|
||||
_operation.annotation().isLValue = false;
|
||||
_operation.annotation().isConstant = false;
|
||||
|
||||
if (_operation.getOperator() == Token::Exp || _operation.getOperator() == Token::SHL)
|
||||
{
|
||||
@ -1646,7 +1645,8 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType(
|
||||
dataLoc = argRefType->location();
|
||||
if (auto type = dynamic_cast<ReferenceType const*>(resultType))
|
||||
resultType = TypeProvider::withLocation(type, dataLoc, type->isPointer());
|
||||
if (argType->isExplicitlyConvertibleTo(*resultType))
|
||||
BoolResult result = argType->isExplicitlyConvertibleTo(*resultType);
|
||||
if (result)
|
||||
{
|
||||
if (auto argArrayType = dynamic_cast<ArrayType const*>(argType))
|
||||
{
|
||||
@ -1717,14 +1717,15 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType(
|
||||
"you can use the .address member of the function."
|
||||
);
|
||||
else
|
||||
m_errorReporter.typeError(
|
||||
m_errorReporter.typeErrorConcatenateDescriptions(
|
||||
9640_error,
|
||||
_functionCall.location(),
|
||||
"Explicit type conversion not allowed from \"" +
|
||||
argType->toString() +
|
||||
"\" to \"" +
|
||||
resultType->toString() +
|
||||
"\"."
|
||||
"\".",
|
||||
result.message()
|
||||
);
|
||||
}
|
||||
if (auto addressType = dynamic_cast<AddressType const*>(resultType))
|
||||
@ -2105,18 +2106,17 @@ void TypeChecker::typeCheckFunctionGeneralChecks(
|
||||
{
|
||||
bool not_all_mapped = false;
|
||||
|
||||
for (size_t i = 0; i < paramArgMap.size(); i++)
|
||||
for (size_t i = 0; i < argumentNames.size(); i++)
|
||||
{
|
||||
size_t j;
|
||||
for (j = 0; j < argumentNames.size(); j++)
|
||||
if (parameterNames[i] == *argumentNames[j])
|
||||
for (j = 0; j < parameterNames.size(); j++)
|
||||
if (parameterNames[j] == *argumentNames[i])
|
||||
break;
|
||||
|
||||
if (j < argumentNames.size())
|
||||
paramArgMap[i] = arguments[j].get();
|
||||
if (j < parameterNames.size())
|
||||
paramArgMap[j] = arguments[i].get();
|
||||
else
|
||||
{
|
||||
paramArgMap[i] = nullptr;
|
||||
not_all_mapped = true;
|
||||
m_errorReporter.typeError(
|
||||
4974_error,
|
||||
@ -2189,7 +2189,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
for (ASTPointer<Expression const> const& argument: arguments)
|
||||
{
|
||||
argument->accept(*this);
|
||||
if (!argument->annotation().isPure)
|
||||
if (!*argument->annotation().isPure)
|
||||
argumentsArePure = false;
|
||||
}
|
||||
|
||||
@ -2212,6 +2212,9 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
// Determine function call kind and function type for this FunctionCall node
|
||||
FunctionCallAnnotation& funcCallAnno = _functionCall.annotation();
|
||||
FunctionTypePointer functionType = nullptr;
|
||||
funcCallAnno.isConstant = false;
|
||||
|
||||
bool isLValue = false;
|
||||
|
||||
// Determine and assign function call kind, lvalue, purity and function type for this FunctionCall node
|
||||
switch (expressionType->category())
|
||||
@ -2223,7 +2226,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
// Purity for function calls also depends upon the callee and its FunctionType
|
||||
funcCallAnno.isPure =
|
||||
argumentsArePure &&
|
||||
_functionCall.expression().annotation().isPure &&
|
||||
*_functionCall.expression().annotation().isPure &&
|
||||
functionType &&
|
||||
functionType->isPure();
|
||||
|
||||
@ -2231,7 +2234,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
functionType->kind() == FunctionType::Kind::ArrayPush ||
|
||||
functionType->kind() == FunctionType::Kind::ByteArrayPush
|
||||
)
|
||||
funcCallAnno.isLValue = functionType->parameterTypes().empty();
|
||||
isLValue = functionType->parameterTypes().empty();
|
||||
|
||||
break;
|
||||
|
||||
@ -2251,13 +2254,11 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
);
|
||||
functionType = dynamic_cast<StructType const&>(*actualType).constructorType();
|
||||
funcCallAnno.kind = FunctionCallKind::StructConstructorCall;
|
||||
funcCallAnno.isPure = argumentsArePure;
|
||||
}
|
||||
else
|
||||
{
|
||||
funcCallAnno.kind = FunctionCallKind::TypeConversion;
|
||||
funcCallAnno.isPure = argumentsArePure;
|
||||
}
|
||||
|
||||
funcCallAnno.isPure = argumentsArePure;
|
||||
|
||||
break;
|
||||
}
|
||||
@ -2270,6 +2271,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
break;
|
||||
}
|
||||
|
||||
funcCallAnno.isLValue = isLValue;
|
||||
|
||||
// Determine return types
|
||||
switch (*funcCallAnno.kind)
|
||||
{
|
||||
@ -2340,6 +2343,9 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions)
|
||||
|
||||
_functionCallOptions.expression().accept(*this);
|
||||
|
||||
_functionCallOptions.annotation().isPure = false;
|
||||
_functionCallOptions.annotation().isConstant = false;
|
||||
|
||||
auto expressionFunctionType = dynamic_cast<FunctionType const*>(type(_functionCallOptions.expression()));
|
||||
if (!expressionFunctionType)
|
||||
{
|
||||
@ -2470,6 +2476,8 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
|
||||
TypePointer type = _newExpression.typeName().annotation().type;
|
||||
solAssert(!!type, "Type name not resolved.");
|
||||
|
||||
_newExpression.annotation().isConstant = false;
|
||||
|
||||
if (auto contractName = dynamic_cast<UserDefinedTypeName const*>(&_newExpression.typeName()))
|
||||
{
|
||||
auto contract = dynamic_cast<ContractDefinition const*>(&dereference(*contractName));
|
||||
@ -2500,6 +2508,7 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
|
||||
}
|
||||
|
||||
_newExpression.annotation().type = FunctionType::newExpressionType(*contract);
|
||||
_newExpression.annotation().isPure = false;
|
||||
}
|
||||
else if (type->category() == Type::Category::Array)
|
||||
{
|
||||
@ -2556,6 +2565,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
|
||||
auto& annotation = _memberAccess.annotation();
|
||||
|
||||
annotation.isConstant = false;
|
||||
|
||||
if (possibleMembers.empty())
|
||||
{
|
||||
if (initialMemberCount == 0 && !dynamic_cast<ArraySliceType const*>(exprType))
|
||||
@ -2688,11 +2699,16 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
functionType &&
|
||||
functionType->kind() == FunctionType::Kind::Declaration
|
||||
)
|
||||
annotation.isPure = _memberAccess.expression().annotation().isPure;
|
||||
annotation.isPure = *_memberAccess.expression().annotation().isPure;
|
||||
}
|
||||
}
|
||||
else if (exprType->category() == Type::Category::Module)
|
||||
annotation.isPure = _memberAccess.expression().annotation().isPure;
|
||||
{
|
||||
annotation.isPure = *_memberAccess.expression().annotation().isPure;
|
||||
annotation.isLValue = false;
|
||||
}
|
||||
else
|
||||
annotation.isLValue = false;
|
||||
|
||||
// TODO some members might be pure, but for example `address(0x123).balance` is not pure
|
||||
// although every subexpression is, so leaving this limited for now.
|
||||
@ -2708,11 +2724,14 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
)
|
||||
if (auto const* parentAccess = dynamic_cast<MemberAccess const*>(&_memberAccess.expression()))
|
||||
{
|
||||
annotation.isPure = parentAccess->expression().annotation().isPure;
|
||||
bool isPure = *parentAccess->expression().annotation().isPure;
|
||||
if (auto const* exprInt = dynamic_cast<Identifier const*>(&parentAccess->expression()))
|
||||
if (exprInt->name() == "this" || exprInt->name() == "super")
|
||||
annotation.isPure = true;
|
||||
isPure = true;
|
||||
|
||||
annotation.isPure = isPure;
|
||||
}
|
||||
|
||||
if (auto magicType = dynamic_cast<MagicType const*>(exprType))
|
||||
{
|
||||
if (magicType->kind() == MagicType::Kind::ABI)
|
||||
@ -2760,16 +2779,20 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
annotation.isPure = true;
|
||||
}
|
||||
|
||||
if (!annotation.isPure.set())
|
||||
annotation.isPure = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TypeChecker::visit(IndexAccess const& _access)
|
||||
{
|
||||
_access.annotation().isConstant = false;
|
||||
_access.baseExpression().accept(*this);
|
||||
TypePointer baseType = type(_access.baseExpression());
|
||||
TypePointer resultType = nullptr;
|
||||
bool isLValue = false;
|
||||
bool isPure = _access.baseExpression().annotation().isPure;
|
||||
bool isPure = *_access.baseExpression().annotation().isPure;
|
||||
Expression const* index = _access.indexExpression();
|
||||
switch (baseType->category())
|
||||
{
|
||||
@ -2871,7 +2894,7 @@ bool TypeChecker::visit(IndexAccess const& _access)
|
||||
}
|
||||
_access.annotation().type = resultType;
|
||||
_access.annotation().isLValue = isLValue;
|
||||
if (index && !index->annotation().isPure)
|
||||
if (index && !*index->annotation().isPure)
|
||||
isPure = false;
|
||||
_access.annotation().isPure = isPure;
|
||||
|
||||
@ -2880,24 +2903,28 @@ bool TypeChecker::visit(IndexAccess const& _access)
|
||||
|
||||
bool TypeChecker::visit(IndexRangeAccess const& _access)
|
||||
{
|
||||
_access.annotation().isConstant = false;
|
||||
_access.baseExpression().accept(*this);
|
||||
|
||||
bool isLValue = false; // TODO: set this correctly when implementing slices for memory and storage arrays
|
||||
bool isPure = _access.baseExpression().annotation().isPure;
|
||||
bool isPure = *_access.baseExpression().annotation().isPure;
|
||||
|
||||
if (Expression const* start = _access.startExpression())
|
||||
{
|
||||
expectType(*start, *TypeProvider::uint256());
|
||||
if (!start->annotation().isPure)
|
||||
if (!*start->annotation().isPure)
|
||||
isPure = false;
|
||||
}
|
||||
if (Expression const* end = _access.endExpression())
|
||||
{
|
||||
expectType(*end, *TypeProvider::uint256());
|
||||
if (!end->annotation().isPure)
|
||||
if (!*end->annotation().isPure)
|
||||
isPure = false;
|
||||
}
|
||||
|
||||
_access.annotation().isLValue = isLValue;
|
||||
_access.annotation().isPure = isPure;
|
||||
|
||||
TypePointer exprType = type(_access.baseExpression());
|
||||
if (exprType->category() == Type::Category::TypeType)
|
||||
{
|
||||
@ -2912,14 +2939,11 @@ bool TypeChecker::visit(IndexRangeAccess const& _access)
|
||||
else if (!(arrayType = dynamic_cast<ArrayType const*>(exprType)))
|
||||
m_errorReporter.fatalTypeError(4781_error, _access.location(), "Index range access is only possible for arrays and array slices.");
|
||||
|
||||
|
||||
if (arrayType->location() != DataLocation::CallData || !arrayType->isDynamicallySized())
|
||||
m_errorReporter.typeError(1227_error, _access.location(), "Index range access is only supported for dynamic calldata arrays.");
|
||||
else if (arrayType->baseType()->isDynamicallyEncoded())
|
||||
m_errorReporter.typeError(2148_error, _access.location(), "Index range access is not supported for arrays with dynamically encoded base types.");
|
||||
_access.annotation().type = TypeProvider::arraySlice(*arrayType);
|
||||
_access.annotation().isLValue = isLValue;
|
||||
_access.annotation().isPure = isPure;
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -3037,20 +3061,22 @@ bool TypeChecker::visit(Identifier const& _identifier)
|
||||
!!annotation.referencedDeclaration,
|
||||
"Referenced declaration is null after overload resolution."
|
||||
);
|
||||
bool isConstant = false;
|
||||
annotation.isLValue = annotation.referencedDeclaration->isLValue();
|
||||
annotation.type = annotation.referencedDeclaration->type();
|
||||
solAssert(annotation.type, "Declaration referenced before type could be determined.");
|
||||
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration))
|
||||
annotation.isPure = annotation.isConstant = variableDeclaration->isConstant();
|
||||
annotation.isPure = isConstant = variableDeclaration->isConstant();
|
||||
else if (dynamic_cast<MagicVariableDeclaration const*>(annotation.referencedDeclaration))
|
||||
{
|
||||
if (dynamic_cast<FunctionType const*>(annotation.type))
|
||||
annotation.isPure = true;
|
||||
}
|
||||
annotation.isPure = dynamic_cast<FunctionType const*>(annotation.type);
|
||||
else if (dynamic_cast<TypeType const*>(annotation.type))
|
||||
annotation.isPure = true;
|
||||
else if (dynamic_cast<ModuleType const*>(annotation.type))
|
||||
annotation.isPure = true;
|
||||
else
|
||||
annotation.isPure = false;
|
||||
|
||||
annotation.isConstant = isConstant;
|
||||
|
||||
// Check for deprecated function names.
|
||||
// The check is done here for the case without an actual function call.
|
||||
@ -3091,6 +3117,8 @@ void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr)
|
||||
{
|
||||
_expr.annotation().type = TypeProvider::typeType(TypeProvider::fromElementaryTypeName(_expr.type().typeName(), _expr.type().stateMutability()));
|
||||
_expr.annotation().isPure = true;
|
||||
_expr.annotation().isLValue = false;
|
||||
_expr.annotation().isConstant = false;
|
||||
}
|
||||
|
||||
void TypeChecker::endVisit(Literal const& _literal)
|
||||
@ -3146,6 +3174,8 @@ void TypeChecker::endVisit(Literal const& _literal)
|
||||
m_errorReporter.fatalTypeError(2826_error, _literal.location(), "Invalid literal value.");
|
||||
|
||||
_literal.annotation().isPure = true;
|
||||
_literal.annotation().isLValue = false;
|
||||
_literal.annotation().isConstant = false;
|
||||
}
|
||||
|
||||
void TypeChecker::endVisit(UsingForDirective const& _usingFor)
|
||||
@ -3189,7 +3219,8 @@ Declaration const& TypeChecker::dereference(UserDefinedTypeName const& _typeName
|
||||
bool TypeChecker::expectType(Expression const& _expression, Type const& _expectedType)
|
||||
{
|
||||
_expression.accept(*this);
|
||||
if (!type(_expression)->isImplicitlyConvertibleTo(_expectedType))
|
||||
BoolResult result = type(_expression)->isImplicitlyConvertibleTo(_expectedType);
|
||||
if (!result)
|
||||
{
|
||||
auto errorMsg = "Type " +
|
||||
type(_expression)->toString() +
|
||||
@ -3208,17 +3239,23 @@ bool TypeChecker::expectType(Expression const& _expression, Type const& _expecte
|
||||
errorMsg + ", but it can be explicitly converted."
|
||||
);
|
||||
else
|
||||
m_errorReporter.typeError(
|
||||
m_errorReporter.typeErrorConcatenateDescriptions(
|
||||
2326_error,
|
||||
_expression.location(),
|
||||
errorMsg +
|
||||
". Try converting to type " +
|
||||
type(_expression)->mobileType()->toString() +
|
||||
" or use an explicit conversion."
|
||||
" or use an explicit conversion.",
|
||||
result.message()
|
||||
);
|
||||
}
|
||||
else
|
||||
m_errorReporter.typeError(7407_error, _expression.location(), errorMsg + ".");
|
||||
m_errorReporter.typeErrorConcatenateDescriptions(
|
||||
7407_error,
|
||||
_expression.location(),
|
||||
errorMsg + ".",
|
||||
result.message()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -3230,11 +3267,11 @@ void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAss
|
||||
_expression.annotation().lValueOfOrdinaryAssignment = _ordinaryAssignment;
|
||||
_expression.accept(*this);
|
||||
|
||||
if (_expression.annotation().isLValue)
|
||||
if (*_expression.annotation().isLValue)
|
||||
return;
|
||||
|
||||
auto [errorId, description] = [&]() -> tuple<ErrorId, string> {
|
||||
if (_expression.annotation().isConstant)
|
||||
if (*_expression.annotation().isConstant)
|
||||
return { 6520_error, "Cannot assign to a constant variable." };
|
||||
|
||||
if (auto indexAccess = dynamic_cast<IndexAccess const*>(&_expression))
|
||||
|
@ -261,21 +261,24 @@ void ViewPureChecker::reportMutability(
|
||||
{
|
||||
// We do not warn for library functions because they cannot be payable anyway.
|
||||
// Also internal functions should be allowed to use `msg.value`.
|
||||
if (m_currentFunction->isPublic() && !m_currentFunction->libraryFunction())
|
||||
if ((m_currentFunction->isConstructor() || m_currentFunction->isPublic()) && !m_currentFunction->libraryFunction())
|
||||
{
|
||||
if (_nestedLocation)
|
||||
m_errorReporter.typeError(
|
||||
4006_error,
|
||||
_location,
|
||||
SecondarySourceLocation().append("\"msg.value\" or \"callvalue()\" appear here inside the modifier.", *_nestedLocation),
|
||||
"This modifier uses \"msg.value\" or \"callvalue()\" and thus the function has to be payable or internal."
|
||||
m_currentFunction->isConstructor() ?
|
||||
"This modifier uses \"msg.value\" or \"callvalue()\" and thus the constructor has to be payable."
|
||||
: "This modifier uses \"msg.value\" or \"callvalue()\" and thus the function has to be payable or internal."
|
||||
);
|
||||
else
|
||||
m_errorReporter.typeError(
|
||||
5887_error,
|
||||
_location,
|
||||
"\"msg.value\" and \"callvalue()\" can only be used in payable public functions. Make the function "
|
||||
"\"payable\" or use an internal function to avoid this error."
|
||||
m_currentFunction->isConstructor() ?
|
||||
"\"msg.value\" and \"callvalue()\" can only be used in payable constructors. Make the constructor \"payable\" to avoid this error."
|
||||
: "\"msg.value\" and \"callvalue()\" can only be used in payable public functions. Make the function \"payable\" or use an internal function to avoid this error."
|
||||
);
|
||||
m_errors = true;
|
||||
}
|
||||
|
@ -208,6 +208,14 @@ vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition:
|
||||
});
|
||||
}
|
||||
|
||||
uint64_t ContractDefinition::interfaceId() const
|
||||
{
|
||||
uint64_t result{0};
|
||||
for (auto const& function: interfaceFunctionList(false))
|
||||
result ^= util::fromBigEndian<uint64_t>(function.first.ref());
|
||||
return result;
|
||||
}
|
||||
|
||||
TypePointer ContractDefinition::type() const
|
||||
{
|
||||
return TypeProvider::typeType(TypeProvider::contract(*this));
|
||||
@ -484,7 +492,7 @@ CallableDeclaration const* Scopable::functionOrModifierDefinition() const
|
||||
|
||||
string Scopable::sourceUnitName() const
|
||||
{
|
||||
return sourceUnit().annotation().path;
|
||||
return *sourceUnit().annotation().path;
|
||||
}
|
||||
|
||||
DeclarationAnnotation& Declaration::annotation() const
|
||||
|
@ -152,10 +152,19 @@ std::vector<T const*> ASTNode::filteredNodes(std::vector<ASTPointer<ASTNode>> co
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract marker class that specifies that this AST node opens a scope.
|
||||
*/
|
||||
class ScopeOpener
|
||||
{
|
||||
public:
|
||||
virtual ~ScopeOpener() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Source unit containing import directives and contract definitions.
|
||||
*/
|
||||
class SourceUnit: public ASTNode
|
||||
class SourceUnit: public ASTNode, public ScopeOpener
|
||||
{
|
||||
public:
|
||||
SourceUnit(
|
||||
@ -455,7 +464,7 @@ protected:
|
||||
* document order. It first visits all struct declarations, then all variable declarations and
|
||||
* finally all function declarations.
|
||||
*/
|
||||
class ContractDefinition: public Declaration, public StructurallyDocumented
|
||||
class ContractDefinition: public Declaration, public StructurallyDocumented, public ScopeOpener
|
||||
{
|
||||
public:
|
||||
ContractDefinition(
|
||||
@ -500,6 +509,8 @@ public:
|
||||
/// as intended for use by the ABI.
|
||||
std::map<util::FixedHash<4>, FunctionTypePointer> interfaceFunctions(bool _includeInheritedFunctions = true) const;
|
||||
std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>> const& interfaceFunctionList(bool _includeInheritedFunctions = true) const;
|
||||
/// @returns the EIP-165 compatible interface identifier. This will exclude inherited functions.
|
||||
uint64_t interfaceId() const;
|
||||
|
||||
/// @returns a list of all declarations in this contract
|
||||
std::vector<Declaration const*> declarations() const { return filteredNodes<Declaration>(m_subNodes); }
|
||||
@ -593,7 +604,7 @@ private:
|
||||
ASTPointer<TypeName> m_typeName;
|
||||
};
|
||||
|
||||
class StructDefinition: public Declaration
|
||||
class StructDefinition: public Declaration, public ScopeOpener
|
||||
{
|
||||
public:
|
||||
StructDefinition(
|
||||
@ -620,7 +631,7 @@ private:
|
||||
std::vector<ASTPointer<VariableDeclaration>> m_members;
|
||||
};
|
||||
|
||||
class EnumDefinition: public Declaration
|
||||
class EnumDefinition: public Declaration, public ScopeOpener
|
||||
{
|
||||
public:
|
||||
EnumDefinition(
|
||||
@ -765,7 +776,7 @@ protected:
|
||||
std::vector<ASTPointer<UserDefinedTypeName>> m_overrides;
|
||||
};
|
||||
|
||||
class FunctionDefinition: public CallableDeclaration, public StructurallyDocumented, public ImplementationOptional
|
||||
class FunctionDefinition: public CallableDeclaration, public StructurallyDocumented, public ImplementationOptional, public ScopeOpener
|
||||
{
|
||||
public:
|
||||
FunctionDefinition(
|
||||
@ -989,7 +1000,7 @@ private:
|
||||
/**
|
||||
* Definition of a function modifier.
|
||||
*/
|
||||
class ModifierDefinition: public CallableDeclaration, public StructurallyDocumented, public ImplementationOptional
|
||||
class ModifierDefinition: public CallableDeclaration, public StructurallyDocumented, public ImplementationOptional, public ScopeOpener
|
||||
{
|
||||
public:
|
||||
ModifierDefinition(
|
||||
@ -1061,7 +1072,7 @@ private:
|
||||
/**
|
||||
* Definition of a (loggable) event.
|
||||
*/
|
||||
class EventDefinition: public CallableDeclaration, public StructurallyDocumented
|
||||
class EventDefinition: public CallableDeclaration, public StructurallyDocumented, public ScopeOpener
|
||||
{
|
||||
public:
|
||||
EventDefinition(
|
||||
@ -1199,7 +1210,7 @@ private:
|
||||
/**
|
||||
* A literal function type. Its source form is "function (paramType1, paramType2) internal / external returns (retType1, retType2)"
|
||||
*/
|
||||
class FunctionTypeName: public TypeName
|
||||
class FunctionTypeName: public TypeName, public ScopeOpener
|
||||
{
|
||||
public:
|
||||
FunctionTypeName(
|
||||
@ -1334,7 +1345,7 @@ private:
|
||||
/**
|
||||
* Brace-enclosed block containing zero or more statements.
|
||||
*/
|
||||
class Block: public Statement, public Scopable
|
||||
class Block: public Statement, public Scopable, public ScopeOpener
|
||||
{
|
||||
public:
|
||||
Block(
|
||||
@ -1411,7 +1422,7 @@ private:
|
||||
* unsuccessful cases.
|
||||
* Names are only allowed for the unsuccessful cases.
|
||||
*/
|
||||
class TryCatchClause: public ASTNode, public Scopable
|
||||
class TryCatchClause: public ASTNode, public Scopable, public ScopeOpener
|
||||
{
|
||||
public:
|
||||
TryCatchClause(
|
||||
@ -1526,7 +1537,7 @@ private:
|
||||
/**
|
||||
* For loop statement
|
||||
*/
|
||||
class ForStatement: public BreakableStatement, public Scopable
|
||||
class ForStatement: public BreakableStatement, public Scopable, public ScopeOpener
|
||||
{
|
||||
public:
|
||||
ForStatement(
|
||||
|
@ -47,6 +47,7 @@ namespace solidity::frontend
|
||||
|
||||
class Type;
|
||||
using TypePointer = Type const*;
|
||||
using namespace util;
|
||||
|
||||
struct ASTAnnotation
|
||||
{
|
||||
@ -88,9 +89,9 @@ struct StructurallyDocumentedAnnotation
|
||||
struct SourceUnitAnnotation: ASTAnnotation
|
||||
{
|
||||
/// The "absolute" (in the compiler sense) path of this source unit.
|
||||
std::string path;
|
||||
SetOnce<std::string> path;
|
||||
/// The exported symbols (all global symbols).
|
||||
std::map<ASTString, std::vector<Declaration const*>> exportedSymbols;
|
||||
SetOnce<std::map<ASTString, std::vector<Declaration const*>>> exportedSymbols;
|
||||
/// Experimental features.
|
||||
std::set<ExperimentalFeature> experimentalFeatures;
|
||||
};
|
||||
@ -108,10 +109,10 @@ struct ScopableAnnotation
|
||||
virtual ~ScopableAnnotation() = default;
|
||||
|
||||
/// The scope this declaration resides in. Can be nullptr if it is the global scope.
|
||||
/// Available only after name and type resolution step.
|
||||
/// Filled by the Scoper.
|
||||
ASTNode const* scope = nullptr;
|
||||
/// Pointer to the contract this declaration resides in. Can be nullptr if the current scope
|
||||
/// is not part of a contract. Available only after name and type resolution step.
|
||||
/// is not part of a contract. Filled by the Scoper.
|
||||
ContractDefinition const* contract = nullptr;
|
||||
};
|
||||
|
||||
@ -122,7 +123,7 @@ struct DeclarationAnnotation: ASTAnnotation, ScopableAnnotation
|
||||
struct ImportAnnotation: DeclarationAnnotation
|
||||
{
|
||||
/// The absolute path of the source unit to import.
|
||||
std::string absolutePath;
|
||||
SetOnce<std::string> absolutePath;
|
||||
/// The actual source unit.
|
||||
SourceUnit const* sourceUnit = nullptr;
|
||||
};
|
||||
@ -130,7 +131,7 @@ struct ImportAnnotation: DeclarationAnnotation
|
||||
struct TypeDeclarationAnnotation: DeclarationAnnotation
|
||||
{
|
||||
/// The name of this type, prefixed by proper namespaces if globally accessible.
|
||||
std::string canonicalName;
|
||||
SetOnce<std::string> canonicalName;
|
||||
};
|
||||
|
||||
struct StructDeclarationAnnotation: TypeDeclarationAnnotation
|
||||
@ -149,7 +150,7 @@ struct StructDeclarationAnnotation: TypeDeclarationAnnotation
|
||||
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation
|
||||
{
|
||||
/// List of functions and modifiers without a body. Can also contain functions from base classes.
|
||||
std::vector<Declaration const*> unimplementedDeclarations;
|
||||
std::optional<std::vector<Declaration const*>> unimplementedDeclarations;
|
||||
/// List of all (direct and indirect) base contracts in order from derived to
|
||||
/// base, including the contract itself.
|
||||
std::vector<ContractDefinition const*> linearizedBaseContracts;
|
||||
@ -243,16 +244,16 @@ struct ExpressionAnnotation: ASTAnnotation
|
||||
/// Inferred type of the expression.
|
||||
TypePointer type = nullptr;
|
||||
/// Whether the expression is a constant variable
|
||||
bool isConstant = false;
|
||||
SetOnce<bool> isConstant;
|
||||
/// Whether the expression is pure, i.e. compile-time constant.
|
||||
bool isPure = false;
|
||||
SetOnce<bool> isPure;
|
||||
/// Whether it is an LValue (i.e. something that can be assigned to).
|
||||
bool isLValue = false;
|
||||
SetOnce<bool> isLValue;
|
||||
/// Whether the expression is used in a context where the LValue is actually required.
|
||||
bool willBeWrittenTo = false;
|
||||
/// Whether the expression is an lvalue that is only assigned.
|
||||
/// Would be false for --, ++, delete, +=, -=, ....
|
||||
bool lValueOfOrdinaryAssignment = false;
|
||||
SetOnce<bool> lValueOfOrdinaryAssignment;
|
||||
|
||||
/// Types and - if given - names of arguments if the expr. is a function
|
||||
/// that is called, used for overload resolution
|
||||
|
@ -38,6 +38,7 @@ namespace solidity::frontend
|
||||
{
|
||||
|
||||
class ASTNode;
|
||||
class ScopeOpener;
|
||||
class SourceUnit;
|
||||
class PragmaDirective;
|
||||
class ImportDirective;
|
||||
|
@ -39,10 +39,33 @@
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity::langutil;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template<typename V, template<typename> typename C>
|
||||
void addIfSet(std::vector<pair<string, Json::Value>>& _attributes, string const& _name, C<V> const& _value)
|
||||
{
|
||||
if constexpr (std::is_same_v<C<V>, solidity::util::SetOnce<V>>)
|
||||
{
|
||||
if (!_value.set())
|
||||
return;
|
||||
}
|
||||
else if constexpr (std::is_same_v<C<V>, optional<V>>)
|
||||
{
|
||||
if (!_value.has_value())
|
||||
return;
|
||||
}
|
||||
|
||||
_attributes.emplace_back(_name, *_value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
@ -181,12 +204,14 @@ void ASTJsonConverter::appendExpressionAttributes(
|
||||
{
|
||||
std::vector<pair<string, Json::Value>> exprAttributes = {
|
||||
make_pair("typeDescriptions", typePointerToJson(_annotation.type)),
|
||||
make_pair("isConstant", _annotation.isConstant),
|
||||
make_pair("isPure", _annotation.isPure),
|
||||
make_pair("isLValue", _annotation.isLValue),
|
||||
make_pair("lValueRequested", _annotation.willBeWrittenTo),
|
||||
make_pair("argumentTypes", typePointerToJson(_annotation.arguments))
|
||||
};
|
||||
|
||||
addIfSet(exprAttributes, "isLValue", _annotation.isLValue);
|
||||
addIfSet(exprAttributes, "isPure", _annotation.isPure);
|
||||
addIfSet(exprAttributes, "isConstant", _annotation.isConstant);
|
||||
|
||||
_attributes += exprAttributes;
|
||||
}
|
||||
|
||||
@ -214,23 +239,27 @@ Json::Value ASTJsonConverter::toJson(ASTNode const& _node)
|
||||
|
||||
bool ASTJsonConverter::visit(SourceUnit const& _node)
|
||||
{
|
||||
Json::Value exportedSymbols = Json::objectValue;
|
||||
for (auto const& sym: _node.annotation().exportedSymbols)
|
||||
std::vector<pair<string, Json::Value>> attributes = {
|
||||
make_pair("license", _node.licenseString() ? Json::Value(*_node.licenseString()) : Json::nullValue),
|
||||
make_pair("nodes", toJson(_node.nodes()))
|
||||
};
|
||||
|
||||
if (_node.annotation().exportedSymbols.set())
|
||||
{
|
||||
exportedSymbols[sym.first] = Json::arrayValue;
|
||||
for (Declaration const* overload: sym.second)
|
||||
exportedSymbols[sym.first].append(nodeId(*overload));
|
||||
}
|
||||
setJsonNode(
|
||||
_node,
|
||||
"SourceUnit",
|
||||
Json::Value exportedSymbols = Json::objectValue;
|
||||
for (auto const& sym: *_node.annotation().exportedSymbols)
|
||||
{
|
||||
make_pair("absolutePath", _node.annotation().path),
|
||||
make_pair("exportedSymbols", move(exportedSymbols)),
|
||||
make_pair("license", _node.licenseString() ? Json::Value(*_node.licenseString()) : Json::nullValue),
|
||||
make_pair("nodes", toJson(_node.nodes()))
|
||||
exportedSymbols[sym.first] = Json::arrayValue;
|
||||
for (Declaration const* overload: sym.second)
|
||||
exportedSymbols[sym.first].append(nodeId(*overload));
|
||||
}
|
||||
);
|
||||
|
||||
attributes.emplace_back("exportedSymbols", exportedSymbols);
|
||||
};
|
||||
|
||||
addIfSet(attributes, "absolutePath", _node.annotation().path);
|
||||
|
||||
setJsonNode(_node, "SourceUnit", std::move(attributes));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -249,10 +278,12 @@ bool ASTJsonConverter::visit(ImportDirective const& _node)
|
||||
{
|
||||
std::vector<pair<string, Json::Value>> attributes = {
|
||||
make_pair("file", _node.path()),
|
||||
make_pair("absolutePath", _node.annotation().absolutePath),
|
||||
make_pair(m_legacy ? "SourceUnit" : "sourceUnit", nodeId(*_node.annotation().sourceUnit)),
|
||||
make_pair("scope", idOrNull(_node.scope()))
|
||||
};
|
||||
|
||||
addIfSet(attributes, "absolutePath", _node.annotation().absolutePath);
|
||||
|
||||
attributes.emplace_back("unitAlias", _node.name());
|
||||
Json::Value symbolAliases(Json::arrayValue);
|
||||
for (auto const& symbolAlias: _node.symbolAliases())
|
||||
@ -270,18 +301,23 @@ bool ASTJsonConverter::visit(ImportDirective const& _node)
|
||||
|
||||
bool ASTJsonConverter::visit(ContractDefinition const& _node)
|
||||
{
|
||||
setJsonNode(_node, "ContractDefinition", {
|
||||
std::vector<pair<string, Json::Value>> attributes = {
|
||||
make_pair("name", _node.name()),
|
||||
make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue),
|
||||
make_pair("contractKind", contractKind(_node.contractKind())),
|
||||
make_pair("abstract", _node.abstract()),
|
||||
make_pair("fullyImplemented", _node.annotation().unimplementedDeclarations.empty()),
|
||||
make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)),
|
||||
make_pair("baseContracts", toJson(_node.baseContracts())),
|
||||
make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies, true)),
|
||||
make_pair("nodes", toJson(_node.subNodes())),
|
||||
make_pair("scope", idOrNull(_node.scope()))
|
||||
});
|
||||
};
|
||||
|
||||
if (_node.annotation().unimplementedDeclarations.has_value())
|
||||
attributes.emplace_back("fullyImplemented", _node.annotation().unimplementedDeclarations->empty());
|
||||
if (!_node.annotation().linearizedBaseContracts.empty())
|
||||
attributes.emplace_back("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts));
|
||||
|
||||
setJsonNode(_node, "ContractDefinition", std::move(attributes));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -305,23 +341,31 @@ bool ASTJsonConverter::visit(UsingForDirective const& _node)
|
||||
|
||||
bool ASTJsonConverter::visit(StructDefinition const& _node)
|
||||
{
|
||||
setJsonNode(_node, "StructDefinition", {
|
||||
std::vector<pair<string, Json::Value>> attributes = {
|
||||
make_pair("name", _node.name()),
|
||||
make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
|
||||
make_pair("canonicalName", _node.annotation().canonicalName),
|
||||
make_pair("members", toJson(_node.members())),
|
||||
make_pair("scope", idOrNull(_node.scope()))
|
||||
});
|
||||
};
|
||||
|
||||
addIfSet(attributes,"canonicalName", _node.annotation().canonicalName);
|
||||
|
||||
setJsonNode(_node, "StructDefinition", std::move(attributes));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ASTJsonConverter::visit(EnumDefinition const& _node)
|
||||
{
|
||||
setJsonNode(_node, "EnumDefinition", {
|
||||
std::vector<pair<string, Json::Value>> attributes = {
|
||||
make_pair("name", _node.name()),
|
||||
make_pair("canonicalName", _node.annotation().canonicalName),
|
||||
make_pair("members", toJson(_node.members()))
|
||||
});
|
||||
};
|
||||
|
||||
addIfSet(attributes,"canonicalName", _node.annotation().canonicalName);
|
||||
|
||||
setJsonNode(_node, "EnumDefinition", std::move(attributes));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,7 @@
|
||||
#include <boost/range/algorithm/copy.hpp>
|
||||
|
||||
#include <limits>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
using namespace std;
|
||||
@ -54,57 +55,6 @@ using namespace solidity::frontend;
|
||||
namespace
|
||||
{
|
||||
|
||||
struct TypeComp
|
||||
{
|
||||
bool operator()(Type const* lhs, Type const* rhs) const
|
||||
{
|
||||
solAssert(lhs && rhs, "");
|
||||
return lhs->richIdentifier() < rhs->richIdentifier();
|
||||
}
|
||||
};
|
||||
using TypeSet = std::set<Type const*, TypeComp>;
|
||||
|
||||
void oversizedSubtypesInner(
|
||||
Type const& _type,
|
||||
bool _includeType,
|
||||
set<StructDefinition const*>& _structsSeen,
|
||||
TypeSet& _oversizedSubtypes
|
||||
)
|
||||
{
|
||||
switch (_type.category())
|
||||
{
|
||||
case Type::Category::Array:
|
||||
{
|
||||
auto const& t = dynamic_cast<ArrayType const&>(_type);
|
||||
if (_includeType && t.storageSizeUpperBound() >= bigint(1) << 64)
|
||||
_oversizedSubtypes.insert(&t);
|
||||
oversizedSubtypesInner(*t.baseType(), t.isDynamicallySized(), _structsSeen, _oversizedSubtypes);
|
||||
break;
|
||||
}
|
||||
case Type::Category::Struct:
|
||||
{
|
||||
auto const& t = dynamic_cast<StructType const&>(_type);
|
||||
if (_structsSeen.count(&t.structDefinition()))
|
||||
return;
|
||||
if (_includeType && t.storageSizeUpperBound() >= bigint(1) << 64)
|
||||
_oversizedSubtypes.insert(&t);
|
||||
_structsSeen.insert(&t.structDefinition());
|
||||
for (auto const& m: t.members(nullptr))
|
||||
oversizedSubtypesInner(*m.type, false, _structsSeen, _oversizedSubtypes);
|
||||
_structsSeen.erase(&t.structDefinition());
|
||||
break;
|
||||
}
|
||||
case Type::Category::Mapping:
|
||||
{
|
||||
auto const* valueType = dynamic_cast<MappingType const&>(_type).valueType();
|
||||
oversizedSubtypesInner(*valueType, true, _structsSeen, _oversizedSubtypes);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether (_base ** _exp) fits into 4096 bits.
|
||||
bool fitsPrecisionExp(bigint const& _base, bigint const& _exp)
|
||||
{
|
||||
@ -201,16 +151,6 @@ util::Result<TypePointers> transformParametersToExternal(TypePointers const& _pa
|
||||
|
||||
}
|
||||
|
||||
vector<frontend::Type const*> solidity::frontend::oversizedSubtypes(frontend::Type const& _type)
|
||||
{
|
||||
set<StructDefinition const*> structsSeen;
|
||||
TypeSet oversized;
|
||||
oversizedSubtypesInner(_type, false, structsSeen, oversized);
|
||||
vector<frontend::Type const*> res;
|
||||
copy(oversized.cbegin(), oversized.cend(), back_inserter(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
void Type::clearCache() const
|
||||
{
|
||||
m_members.clear();
|
||||
@ -404,10 +344,16 @@ TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool) c
|
||||
return encodingType;
|
||||
TypePointer baseType = encodingType;
|
||||
while (auto const* arrayType = dynamic_cast<ArrayType const*>(baseType))
|
||||
{
|
||||
baseType = arrayType->baseType();
|
||||
if (dynamic_cast<StructType const*>(baseType))
|
||||
if (!_encoderV2)
|
||||
|
||||
auto const* baseArrayType = dynamic_cast<ArrayType const*>(baseType);
|
||||
if (!_encoderV2 && baseArrayType && baseArrayType->isDynamicallySized())
|
||||
return nullptr;
|
||||
}
|
||||
if (!_encoderV2 && dynamic_cast<StructType const*>(baseType))
|
||||
return nullptr;
|
||||
|
||||
return encodingType;
|
||||
}
|
||||
|
||||
@ -1405,12 +1351,25 @@ StringLiteralType::StringLiteralType(string _value):
|
||||
BoolResult StringLiteralType::isImplicitlyConvertibleTo(Type const& _convertTo) const
|
||||
{
|
||||
if (auto fixedBytes = dynamic_cast<FixedBytesType const*>(&_convertTo))
|
||||
return static_cast<size_t>(fixedBytes->numBytes()) >= m_value.size();
|
||||
{
|
||||
if (static_cast<size_t>(fixedBytes->numBytes()) < m_value.size())
|
||||
return BoolResult::err("Literal is larger than the type.");
|
||||
return true;
|
||||
}
|
||||
else if (auto arrayType = dynamic_cast<ArrayType const*>(&_convertTo))
|
||||
{
|
||||
size_t invalidSequence;
|
||||
if (arrayType->isString() && !util::validateUTF8(value(), invalidSequence))
|
||||
return BoolResult::err(
|
||||
"Contains invalid UTF-8 sequence at position " +
|
||||
util::toString(invalidSequence) +
|
||||
"."
|
||||
);
|
||||
return
|
||||
arrayType->location() != DataLocation::CallData &&
|
||||
arrayType->isByteArray() &&
|
||||
!(arrayType->dataStoredIn(DataLocation::Storage) && arrayType->isPointer()) &&
|
||||
!(arrayType->isString() && !util::validateUTF8(value()));
|
||||
!(arrayType->dataStoredIn(DataLocation::Storage) && arrayType->isPointer());
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
@ -1431,12 +1390,19 @@ bool StringLiteralType::operator==(Type const& _other) const
|
||||
|
||||
std::string StringLiteralType::toString(bool) const
|
||||
{
|
||||
size_t invalidSequence;
|
||||
auto isPrintableASCII = [](string const& s)
|
||||
{
|
||||
for (auto c: s)
|
||||
{
|
||||
if (static_cast<unsigned>(c) <= 0x1f || static_cast<unsigned>(c) >= 0x7f)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!util::validateUTF8(m_value, invalidSequence))
|
||||
return "literal_string (contains invalid UTF-8 sequence at position " + util::toString(invalidSequence) + ")";
|
||||
|
||||
return "literal_string \"" + m_value + "\"";
|
||||
return isPrintableASCII(m_value) ?
|
||||
("literal_string \"" + m_value + "\"") :
|
||||
("literal_string hex\"" + util::toHex(util::asBytes(m_value)) + "\"");
|
||||
}
|
||||
|
||||
TypePointer StringLiteralType::mobileType() const
|
||||
@ -1569,12 +1535,15 @@ BoolResult ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const
|
||||
return true;
|
||||
if (_convertTo.category() == Category::Contract)
|
||||
{
|
||||
auto const& bases = contractDefinition().annotation().linearizedBaseContracts;
|
||||
if (m_super && bases.size() <= 1)
|
||||
auto const& targetContractType = dynamic_cast<ContractType const&>(_convertTo);
|
||||
if (targetContractType.isSuper())
|
||||
return false;
|
||||
|
||||
auto const& bases = contractDefinition().annotation().linearizedBaseContracts;
|
||||
return find(
|
||||
m_super ? ++bases.begin() : bases.begin(), bases.end(),
|
||||
&dynamic_cast<ContractType const&>(_convertTo).contractDefinition()
|
||||
bases.begin(),
|
||||
bases.end(),
|
||||
&targetContractType.contractDefinition()
|
||||
) != bases.end();
|
||||
}
|
||||
return false;
|
||||
@ -1608,6 +1577,21 @@ TypeResult ContractType::unaryOperatorResult(Token _operator) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
vector<Type const*> CompositeType::fullDecomposition() const
|
||||
{
|
||||
vector<Type const*> res = {this};
|
||||
unordered_set<string> seen = {richIdentifier()};
|
||||
for (size_t k = 0; k < res.size(); ++k)
|
||||
if (auto composite = dynamic_cast<CompositeType const*>(res[k]))
|
||||
for (Type const* next: composite->decomposition())
|
||||
if (seen.count(next->richIdentifier()) == 0)
|
||||
{
|
||||
seen.insert(next->richIdentifier());
|
||||
res.push_back(next);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Type const* ReferenceType::withLocation(DataLocation _location, bool _isPointer) const
|
||||
{
|
||||
return TypeProvider::withLocation(this, _location, _isPointer);
|
||||
@ -2150,7 +2134,7 @@ string ContractType::toString(bool) const
|
||||
|
||||
string ContractType::canonicalName() const
|
||||
{
|
||||
return m_contract.annotation().canonicalName;
|
||||
return *m_contract.annotation().canonicalName;
|
||||
}
|
||||
|
||||
MemberList::MemberMap ContractType::nativeMembers(ASTNode const*) const
|
||||
@ -2401,7 +2385,7 @@ bool StructType::containsNestedMapping() const
|
||||
|
||||
string StructType::toString(bool _short) const
|
||||
{
|
||||
string ret = "struct " + m_struct.annotation().canonicalName;
|
||||
string ret = "struct " + *m_struct.annotation().canonicalName;
|
||||
if (!_short)
|
||||
ret += " " + stringForReferencePart();
|
||||
return ret;
|
||||
@ -2580,7 +2564,7 @@ string StructType::signatureInExternalFunction(bool _structsByName) const
|
||||
|
||||
string StructType::canonicalName() const
|
||||
{
|
||||
return m_struct.annotation().canonicalName;
|
||||
return *m_struct.annotation().canonicalName;
|
||||
}
|
||||
|
||||
FunctionTypePointer StructType::constructorType() const
|
||||
@ -2645,6 +2629,13 @@ vector<tuple<string, TypePointer>> StructType::makeStackItems() const
|
||||
solAssert(false, "");
|
||||
}
|
||||
|
||||
vector<Type const*> StructType::decomposition() const
|
||||
{
|
||||
vector<Type const*> res;
|
||||
for (MemberList::Member const& member: members(nullptr))
|
||||
res.push_back(member.type);
|
||||
return res;
|
||||
}
|
||||
|
||||
TypePointer EnumType::encodingType() const
|
||||
{
|
||||
@ -2680,12 +2671,12 @@ unsigned EnumType::storageBytes() const
|
||||
|
||||
string EnumType::toString(bool) const
|
||||
{
|
||||
return string("enum ") + m_enum.annotation().canonicalName;
|
||||
return string("enum ") + *m_enum.annotation().canonicalName;
|
||||
}
|
||||
|
||||
string EnumType::canonicalName() const
|
||||
{
|
||||
return m_enum.annotation().canonicalName;
|
||||
return *m_enum.annotation().canonicalName;
|
||||
}
|
||||
|
||||
size_t EnumType::numberOfMembers() const
|
||||
@ -3007,7 +2998,7 @@ TypePointers FunctionType::returnParameterTypesWithoutDynamicTypes() const
|
||||
m_kind == Kind::BareStaticCall
|
||||
)
|
||||
for (auto& param: returnParameterTypes)
|
||||
if (param->isDynamicallySized() && !param->dataStoredIn(DataLocation::Storage))
|
||||
if (param->isDynamicallyEncoded() && !param->dataStoredIn(DataLocation::Storage))
|
||||
param = TypeProvider::inaccessibleDynamic();
|
||||
|
||||
return returnParameterTypes;
|
||||
@ -3158,7 +3149,7 @@ string FunctionType::toString(bool _short) const
|
||||
auto const* functionDefinition = dynamic_cast<FunctionDefinition const*>(m_declaration);
|
||||
solAssert(functionDefinition, "");
|
||||
if (auto const* contract = dynamic_cast<ContractDefinition const*>(functionDefinition->scope()))
|
||||
name += contract->annotation().canonicalName + ".";
|
||||
name += *contract->annotation().canonicalName + ".";
|
||||
name += functionDefinition->name();
|
||||
}
|
||||
name += '(';
|
||||
@ -3482,12 +3473,12 @@ bool FunctionType::canTakeArguments(
|
||||
|
||||
size_t matchedNames = 0;
|
||||
|
||||
for (auto const& argName: _arguments.names)
|
||||
for (size_t i = 0; i < paramNames.size(); i++)
|
||||
if (*argName == paramNames[i])
|
||||
for (size_t a = 0; a < _arguments.names.size(); a++)
|
||||
for (size_t p = 0; p < paramNames.size(); p++)
|
||||
if (*_arguments.names[a] == paramNames[p])
|
||||
{
|
||||
matchedNames++;
|
||||
if (!_arguments.types[i]->isImplicitlyConvertibleTo(*paramTypes[i]))
|
||||
if (!_arguments.types[a]->isImplicitlyConvertibleTo(*paramTypes[p]))
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -3949,7 +3940,7 @@ bool ModuleType::operator==(Type const& _other) const
|
||||
MemberList::MemberMap ModuleType::nativeMembers(ASTNode const*) const
|
||||
{
|
||||
MemberList::MemberMap symbols;
|
||||
for (auto const& symbolName: m_sourceUnit.annotation().exportedSymbols)
|
||||
for (auto const& symbolName: *m_sourceUnit.annotation().exportedSymbols)
|
||||
for (Declaration const* symbol: symbolName.second)
|
||||
symbols.emplace_back(symbolName.first, symbol->type(), symbol);
|
||||
return symbols;
|
||||
@ -3957,7 +3948,7 @@ MemberList::MemberMap ModuleType::nativeMembers(ASTNode const*) const
|
||||
|
||||
string ModuleType::toString(bool) const
|
||||
{
|
||||
return string("module \"") + m_sourceUnit.annotation().path + string("\"");
|
||||
return string("module \"") + *m_sourceUnit.annotation().path + string("\"");
|
||||
}
|
||||
|
||||
string MagicType::richIdentifier() const
|
||||
|
@ -60,8 +60,6 @@ using BoolResult = util::Result<bool>;
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
std::vector<frontend::Type const*> oversizedSubtypes(frontend::Type const& _type);
|
||||
|
||||
inline rational makeRational(bigint const& _numerator, bigint const& _denominator)
|
||||
{
|
||||
solAssert(_denominator != 0, "division by zero");
|
||||
@ -694,11 +692,37 @@ public:
|
||||
TypeResult interfaceType(bool) const override { return this; }
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for types which can be thought of as several elements of other types put together.
|
||||
* For example a struct is composed of its members, an array is composed of multiple copies of its
|
||||
* base element and a mapping is composed of its value type elements (note that keys are not
|
||||
* stored anywhere).
|
||||
*/
|
||||
class CompositeType: public Type
|
||||
{
|
||||
protected:
|
||||
CompositeType() = default;
|
||||
|
||||
public:
|
||||
/// @returns a list containing the type itself, elements of its decomposition,
|
||||
/// elements of decomposition of these elements and so on, up to non-composite types.
|
||||
/// Each type is included only once.
|
||||
std::vector<Type const*> fullDecomposition() const;
|
||||
|
||||
protected:
|
||||
/// @returns a list of types that together make up the data part of this type.
|
||||
/// Contains all types that will have to be implicitly stored, whenever an object of this type is stored.
|
||||
/// In particular, it returns the base type for arrays and array slices, the member types for structs,
|
||||
/// the component types for tuples and the value type for mappings
|
||||
/// (note that the key type of a mapping is *not* part of the list).
|
||||
virtual std::vector<Type const*> decomposition() const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class used by types which are not value types and can be stored either in storage, memory
|
||||
* or calldata. This is currently used by arrays and structs.
|
||||
*/
|
||||
class ReferenceType: public Type
|
||||
class ReferenceType: public CompositeType
|
||||
{
|
||||
protected:
|
||||
explicit ReferenceType(DataLocation _location): m_location(_location) {}
|
||||
@ -829,6 +853,8 @@ public:
|
||||
|
||||
protected:
|
||||
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
|
||||
std::vector<Type const*> decomposition() const override { return {m_baseType}; }
|
||||
|
||||
private:
|
||||
/// String is interpreted as a subtype of Bytes.
|
||||
enum class ArrayKind { Ordinary, Bytes, String };
|
||||
@ -869,6 +895,8 @@ public:
|
||||
|
||||
protected:
|
||||
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
|
||||
std::vector<Type const*> decomposition() const override { return {m_arrayType.baseType()}; }
|
||||
|
||||
private:
|
||||
ArrayType const& m_arrayType;
|
||||
};
|
||||
@ -994,6 +1022,8 @@ public:
|
||||
|
||||
protected:
|
||||
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
|
||||
std::vector<Type const*> decomposition() const override;
|
||||
|
||||
private:
|
||||
StructDefinition const& m_struct;
|
||||
// Caches for interfaceType(bool)
|
||||
@ -1044,7 +1074,7 @@ private:
|
||||
* Type that can hold a finite sequence of values of different types.
|
||||
* In some cases, the components are empty pointers (when used as placeholders).
|
||||
*/
|
||||
class TupleType: public Type
|
||||
class TupleType: public CompositeType
|
||||
{
|
||||
public:
|
||||
explicit TupleType(std::vector<TypePointer> _types = {}): m_components(std::move(_types)) {}
|
||||
@ -1067,6 +1097,16 @@ public:
|
||||
|
||||
protected:
|
||||
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
|
||||
std::vector<Type const*> decomposition() const override
|
||||
{
|
||||
// Currently calling TupleType::decomposition() is not expected, because we cannot declare a variable of a tuple type.
|
||||
// If that changes, before removing the solAssert, make sure the function does the right thing and is used properly.
|
||||
// Note that different tuple members can have different data locations, so using decomposition() to check
|
||||
// the tuple validity for a data location might require special care.
|
||||
solUnimplemented("Tuple decomposition is not expected.");
|
||||
return m_components;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<TypePointer> const m_components;
|
||||
};
|
||||
@ -1349,7 +1389,7 @@ private:
|
||||
* The type of a mapping, there is one distinct type per key/value type pair.
|
||||
* Mappings always occupy their own storage slot, but do not actually use it.
|
||||
*/
|
||||
class MappingType: public Type
|
||||
class MappingType: public CompositeType
|
||||
{
|
||||
public:
|
||||
MappingType(Type const* _keyType, Type const* _valueType):
|
||||
@ -1373,6 +1413,9 @@ public:
|
||||
Type const* keyType() const { return m_keyType; }
|
||||
Type const* valueType() const { return m_valueType; }
|
||||
|
||||
protected:
|
||||
std::vector<Type const*> decomposition() const override { return {m_valueType}; }
|
||||
|
||||
private:
|
||||
TypePointer m_keyType;
|
||||
TypePointer m_valueType;
|
||||
|
@ -51,8 +51,8 @@ void Compiler::compileContract(
|
||||
|
||||
m_context.optimise(m_optimiserSettings);
|
||||
|
||||
solAssert(m_context.requestedYulFunctionsRan(), "requestedYulFunctions() was not called.");
|
||||
solAssert(m_runtimeContext.requestedYulFunctionsRan(), "requestedYulFunctions() was not called.");
|
||||
solAssert(m_context.appendYulUtilityFunctionsRan(), "appendYulUtilityFunctions() was not called.");
|
||||
solAssert(m_runtimeContext.appendYulUtilityFunctionsRan(), "appendYulUtilityFunctions() was not called.");
|
||||
}
|
||||
|
||||
std::shared_ptr<evmasm::Assembly> Compiler::runtimeAssemblyPtr() const
|
||||
|
@ -74,6 +74,9 @@ public:
|
||||
/// @returns Assembly items of the runtime compiler context
|
||||
evmasm::AssemblyItems const& runtimeAssemblyItems() const { return m_context.assembly().sub(m_runtimeSub).items(); }
|
||||
|
||||
std::string generatedYulUtilityCode() const { return m_context.generatedYulUtilityCode(); }
|
||||
std::string runtimeGeneratedYulUtilityCode() const { return m_runtimeContext.generatedYulUtilityCode(); }
|
||||
|
||||
/// @returns the entry label of the given function. Might return an AssemblyItem of type
|
||||
/// UndefinedItem if it does not exist yet.
|
||||
evmasm::AssemblyItem functionEntryLabel(FunctionDefinition const& _function) const;
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <libsolidity/interface/Version.h>
|
||||
|
||||
#include <libyul/AsmParser.h>
|
||||
#include <libyul/AsmPrinter.h>
|
||||
#include <libyul/AsmAnalysis.h>
|
||||
#include <libyul/AsmAnalysisInfo.h>
|
||||
#include <libyul/backends/evm/AsmCodeGen.h>
|
||||
@ -37,6 +38,7 @@
|
||||
#include <libyul/optimiser/Suite.h>
|
||||
#include <libyul/Object.h>
|
||||
#include <libyul/YulString.h>
|
||||
#include <libyul/Utilities.h>
|
||||
|
||||
#include <libsolutil/Whiskers.h>
|
||||
|
||||
@ -51,9 +53,6 @@
|
||||
|
||||
// Change to "define" to output all intermediate code
|
||||
#undef SOL_OUTPUT_ASM
|
||||
#ifdef SOL_OUTPUT_ASM
|
||||
#include <libyul/AsmPrinter.h>
|
||||
#endif
|
||||
|
||||
|
||||
using namespace std;
|
||||
@ -191,14 +190,24 @@ void CompilerContext::appendMissingLowLevelFunctions()
|
||||
}
|
||||
}
|
||||
|
||||
pair<string, set<string>> CompilerContext::requestedYulFunctions()
|
||||
void CompilerContext::appendYulUtilityFunctions(OptimiserSettings const& _optimiserSettings)
|
||||
{
|
||||
solAssert(!m_requestedYulFunctionsRan, "requestedYulFunctions called more than once.");
|
||||
m_requestedYulFunctionsRan = true;
|
||||
solAssert(!m_appendYulUtilityFunctionsRan, "requestedYulFunctions called more than once.");
|
||||
m_appendYulUtilityFunctionsRan = true;
|
||||
|
||||
set<string> empty;
|
||||
swap(empty, m_externallyUsedYulFunctions);
|
||||
return {m_yulFunctionCollector.requestedFunctions(), std::move(empty)};
|
||||
string code = m_yulFunctionCollector.requestedFunctions();
|
||||
if (!code.empty())
|
||||
{
|
||||
appendInlineAssembly(
|
||||
yul::reindent("{\n" + move(code) + "\n}"),
|
||||
{},
|
||||
m_externallyUsedYulFunctions,
|
||||
true,
|
||||
_optimiserSettings,
|
||||
yulUtilityFileName()
|
||||
);
|
||||
solAssert(!m_generatedYulUtilityCode.empty(), "");
|
||||
}
|
||||
}
|
||||
|
||||
void CompilerContext::addVariable(
|
||||
@ -369,7 +378,8 @@ void CompilerContext::appendInlineAssembly(
|
||||
vector<string> const& _localVariables,
|
||||
set<string> const& _externallyUsedFunctions,
|
||||
bool _system,
|
||||
OptimiserSettings const& _optimiserSettings
|
||||
OptimiserSettings const& _optimiserSettings,
|
||||
string _sourceName
|
||||
)
|
||||
{
|
||||
unsigned startStackHeight = stackHeight();
|
||||
@ -420,7 +430,7 @@ void CompilerContext::appendInlineAssembly(
|
||||
|
||||
ErrorList errors;
|
||||
ErrorReporter errorReporter(errors);
|
||||
auto scanner = make_shared<langutil::Scanner>(langutil::CharStream(_assembly, "--CODEGEN--"));
|
||||
auto scanner = make_shared<langutil::Scanner>(langutil::CharStream(_assembly, _sourceName));
|
||||
yul::EVMDialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(m_evmVersion);
|
||||
optional<langutil::SourceLocation> locationOverride;
|
||||
if (!_system)
|
||||
@ -469,6 +479,17 @@ void CompilerContext::appendInlineAssembly(
|
||||
|
||||
optimizeYul(obj, dialect, _optimiserSettings, externallyUsedIdentifiers);
|
||||
|
||||
if (_system)
|
||||
{
|
||||
// Store as generated sources, but first re-parse to update the source references.
|
||||
solAssert(m_generatedYulUtilityCode.empty(), "");
|
||||
m_generatedYulUtilityCode = yul::AsmPrinter(dialect)(*obj.code);
|
||||
string code = yul::AsmPrinter{dialect}(*obj.code);
|
||||
scanner = make_shared<langutil::Scanner>(langutil::CharStream(m_generatedYulUtilityCode, _sourceName));
|
||||
obj.code = yul::Parser(errorReporter, dialect).parse(scanner, false);
|
||||
*obj.analysisInfo = yul::AsmAnalyzer::analyzeStrictAssertCorrect(dialect, obj);
|
||||
}
|
||||
|
||||
analysisInfo = std::move(*obj.analysisInfo);
|
||||
parserResult = std::move(obj.code);
|
||||
|
||||
@ -477,6 +498,12 @@ void CompilerContext::appendInlineAssembly(
|
||||
cout << yul::AsmPrinter(&dialect)(*parserResult) << endl;
|
||||
#endif
|
||||
}
|
||||
else if (_system)
|
||||
{
|
||||
// Store as generated source.
|
||||
solAssert(m_generatedYulUtilityCode.empty(), "");
|
||||
m_generatedYulUtilityCode = _assembly;
|
||||
}
|
||||
|
||||
if (!errorReporter.errors().empty())
|
||||
reportError("Failed to analyze inline assembly block.");
|
||||
|
@ -163,12 +163,14 @@ public:
|
||||
void appendMissingLowLevelFunctions();
|
||||
ABIFunctions& abiFunctions() { return m_abiFunctions; }
|
||||
YulUtilFunctions& utilFunctions() { return m_yulUtilFunctions; }
|
||||
/// @returns concatenation of all generated functions and a set of the
|
||||
/// externally used functions.
|
||||
/// Clears the internal list, i.e. calling it again will result in an
|
||||
/// empty return value.
|
||||
std::pair<std::string, std::set<std::string>> requestedYulFunctions();
|
||||
bool requestedYulFunctionsRan() const { return m_requestedYulFunctionsRan; }
|
||||
|
||||
/// Appends concatenation of all generated Yul functions to the bytecode
|
||||
/// and stores the Yul source code to be returned by @a generatedYulUtilityCode.
|
||||
/// Should be called exactly once on each context.
|
||||
void appendYulUtilityFunctions(OptimiserSettings const& _optimiserSettings);
|
||||
bool appendYulUtilityFunctionsRan() const { return m_appendYulUtilityFunctionsRan; }
|
||||
std::string const& generatedYulUtilityCode() const { return m_generatedYulUtilityCode; }
|
||||
static std::string yulUtilityFileName() { return "#utility.yul"; }
|
||||
|
||||
/// Returns the distance of the given local variable from the bottom of the stack (of the current function).
|
||||
unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const;
|
||||
@ -246,17 +248,21 @@ public:
|
||||
CompilerContext& operator<<(u256 const& _value) { m_asm->append(_value); return *this; }
|
||||
CompilerContext& operator<<(bytes const& _data) { m_asm->append(_data); return *this; }
|
||||
|
||||
/// Appends inline assembly (strict mode).
|
||||
/// @a _replacements are string-matching replacements that are performed prior to parsing the inline assembly.
|
||||
/// Appends inline assembly (strict-EVM dialect for the current version).
|
||||
/// @param _assembly the assembly text, should be a block.
|
||||
/// @param _localVariables assigns stack positions to variables with the last one being the stack top
|
||||
/// @param _externallyUsedFunctions a set of function names that are not to be renamed or removed.
|
||||
/// @param _system if true, this is a "system-level" assembly where all functions use named labels.
|
||||
/// @param _system if true, this is a "system-level" assembly where all functions use named labels
|
||||
/// and the code is marked to be exported as "compiler-generated assembly utility file".
|
||||
/// @param _optimiserSettings settings for the Yul optimiser, which is run in this function already.
|
||||
/// @param _sourceName the name of the assembly file to be used for source locations
|
||||
void appendInlineAssembly(
|
||||
std::string const& _assembly,
|
||||
std::vector<std::string> const& _localVariables = std::vector<std::string>(),
|
||||
std::set<std::string> const& _externallyUsedFunctions = std::set<std::string>(),
|
||||
bool _system = false,
|
||||
OptimiserSettings const& _optimiserSettings = OptimiserSettings::none()
|
||||
OptimiserSettings const& _optimiserSettings = OptimiserSettings::none(),
|
||||
std::string _sourceName = "--CODEGEN--"
|
||||
);
|
||||
|
||||
/// If m_revertStrings is debug, @returns inline assembly code that
|
||||
@ -385,14 +391,17 @@ private:
|
||||
MultiUseYulFunctionCollector m_yulFunctionCollector;
|
||||
/// Set of externally used yul functions.
|
||||
std::set<std::string> m_externallyUsedYulFunctions;
|
||||
/// Generated Yul code used as utility. Source references from the bytecode can point here.
|
||||
/// Produced from @a m_yulFunctionCollector.
|
||||
std::string m_generatedYulUtilityCode;
|
||||
/// Container for ABI functions to be generated.
|
||||
ABIFunctions m_abiFunctions;
|
||||
/// Container for Yul Util functions to be generated.
|
||||
YulUtilFunctions m_yulUtilFunctions;
|
||||
/// The queue of low-level functions to generate.
|
||||
std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue;
|
||||
/// Flag to check that requestedYulFunctions() was called exactly once
|
||||
bool m_requestedYulFunctionsRan = false;
|
||||
/// Flag to check that appendYulUtilityFunctions() was called exactly once
|
||||
bool m_appendYulUtilityFunctionsRan = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -1272,15 +1272,7 @@ void ContractCompiler::appendMissingFunctions()
|
||||
solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?");
|
||||
}
|
||||
m_context.appendMissingLowLevelFunctions();
|
||||
auto [yulFunctions, externallyUsedYulFunctions] = m_context.requestedYulFunctions();
|
||||
if (!yulFunctions.empty())
|
||||
m_context.appendInlineAssembly(
|
||||
"{" + move(yulFunctions) + "}",
|
||||
{},
|
||||
externallyUsedYulFunctions,
|
||||
true,
|
||||
m_optimiserSettings
|
||||
);
|
||||
m_context.appendYulUtilityFunctions(m_optimiserSettings);
|
||||
}
|
||||
|
||||
void ContractCompiler::appendModifierOrFunctionCode()
|
||||
|
@ -143,6 +143,7 @@ private:
|
||||
/// Pointer to the runtime compiler in case this is a creation compiler.
|
||||
ContractCompiler* m_runtimeCompiler = nullptr;
|
||||
CompilerContext& m_context;
|
||||
|
||||
/// Tag to jump to for a "break" statement and the stack height after freeing the local loop variables.
|
||||
std::vector<std::pair<evmasm::AssemblyItem, unsigned>> m_breakTags;
|
||||
/// Tag to jump to for a "continue" statement and the stack height after freeing the local loop variables.
|
||||
|
@ -779,7 +779,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
solAssert(function.parameterTypes().size() == 1, "");
|
||||
if (m_context.revertStrings() == RevertStrings::Strip)
|
||||
{
|
||||
if (!arguments.front()->annotation().isPure)
|
||||
if (!*arguments.front()->annotation().isPure)
|
||||
{
|
||||
arguments.front()->accept(*this);
|
||||
utils().popStackElement(*arguments.front()->annotation().type);
|
||||
@ -1078,7 +1078,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
solAssert(function.kind() == FunctionType::Kind::Require, "");
|
||||
if (m_context.revertStrings() == RevertStrings::Strip)
|
||||
{
|
||||
if (!arguments.at(1)->annotation().isPure)
|
||||
if (!*arguments.at(1)->annotation().isPure)
|
||||
{
|
||||
arguments.at(1)->accept(*this);
|
||||
utils().popStackElement(*arguments.at(1)->annotation().type);
|
||||
@ -1598,10 +1598,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
{
|
||||
TypePointer arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
|
||||
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition();
|
||||
uint64_t result{0};
|
||||
for (auto const& function: contract.interfaceFunctionList(false))
|
||||
result ^= fromBigEndian<uint64_t>(function.first.ref());
|
||||
m_context << (u256{result} << (256 - 32));
|
||||
m_context << (u256{contract.interfaceId()} << (256 - 32));
|
||||
}
|
||||
else if (member == "min" || member == "max")
|
||||
{
|
||||
|
@ -118,10 +118,10 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess
|
||||
if (!_messageType)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(condition) {
|
||||
if iszero(condition) { <invalidOrRevert> }
|
||||
if iszero(condition) { <error> }
|
||||
}
|
||||
)")
|
||||
("invalidOrRevert", _assert ? "invalid()" : "revert(0, 0)")
|
||||
("error", _assert ? panicFunction() + "()" : "revert(0, 0)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
|
||||
@ -457,7 +457,7 @@ string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
|
||||
string functionName = "checked_add_" + _type.identifier();
|
||||
// TODO: Consider to add a special case for unsigned 256-bit integers
|
||||
// and use the following instead:
|
||||
// sum := add(x, y) if lt(sum, x) { revert(0, 0) }
|
||||
// sum := add(x, y) if lt(sum, x) { <panic>() }
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
@ -466,12 +466,12 @@ string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
|
||||
y := <cleanupFunction>(y)
|
||||
<?signed>
|
||||
// overflow, if x >= 0 and y > (maxValue - x)
|
||||
if and(iszero(slt(x, 0)), sgt(y, sub(<maxValue>, x))) { revert(0, 0) }
|
||||
if and(iszero(slt(x, 0)), sgt(y, sub(<maxValue>, x))) { <panic>() }
|
||||
// underflow, if x < 0 and y < (minValue - x)
|
||||
if and(slt(x, 0), slt(y, sub(<minValue>, x))) { revert(0, 0) }
|
||||
if and(slt(x, 0), slt(y, sub(<minValue>, x))) { <panic>() }
|
||||
<!signed>
|
||||
// overflow, if x > (maxValue - y)
|
||||
if gt(x, sub(<maxValue>, y)) { revert(0, 0) }
|
||||
if gt(x, sub(<maxValue>, y)) { <panic>() }
|
||||
</signed>
|
||||
sum := add(x, y)
|
||||
}
|
||||
@ -481,6 +481,7 @@ string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
|
||||
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
|
||||
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
|
||||
("cleanupFunction", cleanupFunction(_type))
|
||||
("panic", panicFunction())
|
||||
.render();
|
||||
});
|
||||
}
|
||||
@ -497,16 +498,16 @@ string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
|
||||
y := <cleanupFunction>(y)
|
||||
<?signed>
|
||||
// overflow, if x > 0, y > 0 and x > (maxValue / y)
|
||||
if and(and(sgt(x, 0), sgt(y, 0)), gt(x, div(<maxValue>, y))) { revert(0, 0) }
|
||||
if and(and(sgt(x, 0), sgt(y, 0)), gt(x, div(<maxValue>, y))) { <panic>() }
|
||||
// underflow, if x > 0, y < 0 and y < (minValue / x)
|
||||
if and(and(sgt(x, 0), slt(y, 0)), slt(y, sdiv(<minValue>, x))) { revert(0, 0) }
|
||||
if and(and(sgt(x, 0), slt(y, 0)), slt(y, sdiv(<minValue>, x))) { <panic>() }
|
||||
// underflow, if x < 0, y > 0 and x < (minValue / y)
|
||||
if and(and(slt(x, 0), sgt(y, 0)), slt(x, sdiv(<minValue>, y))) { revert(0, 0) }
|
||||
if and(and(slt(x, 0), sgt(y, 0)), slt(x, sdiv(<minValue>, y))) { <panic>() }
|
||||
// overflow, if x < 0, y < 0 and x < (maxValue / y)
|
||||
if and(and(slt(x, 0), slt(y, 0)), slt(x, sdiv(<maxValue>, y))) { revert(0, 0) }
|
||||
if and(and(slt(x, 0), slt(y, 0)), slt(x, sdiv(<maxValue>, y))) { <panic>() }
|
||||
<!signed>
|
||||
// overflow, if x != 0 and y > (maxValue / x)
|
||||
if and(iszero(iszero(x)), gt(y, div(<maxValue>, x))) { revert(0, 0) }
|
||||
if and(iszero(iszero(x)), gt(y, div(<maxValue>, x))) { <panic>() }
|
||||
</signed>
|
||||
product := mul(x, y)
|
||||
}
|
||||
@ -516,6 +517,7 @@ string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
|
||||
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
|
||||
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
|
||||
("cleanupFunction", cleanupFunction(_type))
|
||||
("panic", panicFunction())
|
||||
.render();
|
||||
});
|
||||
}
|
||||
@ -529,13 +531,13 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
|
||||
function <functionName>(x, y) -> r {
|
||||
x := <cleanupFunction>(x)
|
||||
y := <cleanupFunction>(y)
|
||||
if iszero(y) { revert(0, 0) }
|
||||
if iszero(y) { <panic>() }
|
||||
<?signed>
|
||||
// overflow for minVal / -1
|
||||
if and(
|
||||
eq(x, <minVal>),
|
||||
eq(y, sub(0, 1))
|
||||
) { revert(0, 0) }
|
||||
) { <panic>() }
|
||||
</signed>
|
||||
r := <?signed>s</signed>div(x, y)
|
||||
}
|
||||
@ -544,6 +546,7 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
|
||||
("signed", _type.isSigned())
|
||||
("minVal", toCompactHexWithPrefix(u256(_type.minValue())))
|
||||
("cleanupFunction", cleanupFunction(_type))
|
||||
("panic", panicFunction())
|
||||
.render();
|
||||
});
|
||||
}
|
||||
@ -557,13 +560,14 @@ string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type)
|
||||
function <functionName>(x, y) -> r {
|
||||
x := <cleanupFunction>(x)
|
||||
y := <cleanupFunction>(y)
|
||||
if iszero(y) { revert(0, 0) }
|
||||
if iszero(y) { <panic>() }
|
||||
r := <?signed>s</signed>mod(x, y)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("signed", _type.isSigned())
|
||||
("cleanupFunction", cleanupFunction(_type))
|
||||
("panic", panicFunction())
|
||||
.render();
|
||||
});
|
||||
}
|
||||
@ -579,11 +583,11 @@ string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
|
||||
y := <cleanupFunction>(y)
|
||||
<?signed>
|
||||
// underflow, if y >= 0 and x < (minValue + y)
|
||||
if and(iszero(slt(y, 0)), slt(x, add(<minValue>, y))) { revert(0, 0) }
|
||||
if and(iszero(slt(y, 0)), slt(x, add(<minValue>, y))) { <panic>() }
|
||||
// overflow, if y < 0 and x > (maxValue + y)
|
||||
if and(slt(y, 0), sgt(x, add(<maxValue>, y))) { revert(0, 0) }
|
||||
if and(slt(y, 0), sgt(x, add(<maxValue>, y))) { <panic>() }
|
||||
<!signed>
|
||||
if lt(x, y) { revert(0, 0) }
|
||||
if lt(x, y) { <panic>() }
|
||||
</signed>
|
||||
diff := sub(x, y)
|
||||
}
|
||||
@ -593,6 +597,7 @@ string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
|
||||
("maxValue", toCompactHexWithPrefix(u256(_type.maxValue())))
|
||||
("minValue", toCompactHexWithPrefix(u256(_type.minValue())))
|
||||
("cleanupFunction", cleanupFunction(_type))
|
||||
("panic", panicFunction())
|
||||
.render();
|
||||
});
|
||||
}
|
||||
@ -632,6 +637,16 @@ string YulUtilFunctions::overflowCheckedIntExpFunction(
|
||||
|
||||
string YulUtilFunctions::overflowCheckedUnsignedExpFunction()
|
||||
{
|
||||
// Checks for the "small number specialization" below.
|
||||
using namespace boost::multiprecision;
|
||||
solAssert(pow(bigint(10), 77) < pow(bigint(2), 256), "");
|
||||
solAssert(pow(bigint(11), 77) >= pow(bigint(2), 256), "");
|
||||
solAssert(pow(bigint(10), 78) >= pow(bigint(2), 256), "");
|
||||
|
||||
solAssert(pow(bigint(306), 31) < pow(bigint(2), 256), "");
|
||||
solAssert(pow(bigint(307), 31) >= pow(bigint(2), 256), "");
|
||||
solAssert(pow(bigint(306), 32) >= pow(bigint(2), 256), "");
|
||||
|
||||
string functionName = "checked_exp_unsigned";
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return
|
||||
@ -644,25 +659,36 @@ string YulUtilFunctions::overflowCheckedUnsignedExpFunction()
|
||||
if iszero(exponent) { power := 1 leave }
|
||||
if iszero(base) { power := 0 leave }
|
||||
|
||||
power := 1
|
||||
|
||||
for { } gt(exponent, 1) {}
|
||||
// Specializations for small bases
|
||||
switch base
|
||||
// 0 is handled above
|
||||
case 1 { power := 1 leave }
|
||||
case 2
|
||||
{
|
||||
// overflow check for base * base
|
||||
if gt(base, div(max, base)) { revert(0, 0) }
|
||||
if and(exponent, 1)
|
||||
{
|
||||
// no check needed here because base >= power
|
||||
power := mul(power, base)
|
||||
}
|
||||
base := mul(base, base)
|
||||
exponent := <shr_1>(exponent)
|
||||
if gt(exponent, 255) { <panic>() }
|
||||
power := exp(2, exponent)
|
||||
if gt(power, max) { <panic>() }
|
||||
leave
|
||||
}
|
||||
if gt(power, div(max, base)) { revert(0, 0) }
|
||||
if or(
|
||||
and(lt(base, 11), lt(exponent, 78)),
|
||||
and(lt(base, 307), lt(exponent, 32))
|
||||
)
|
||||
{
|
||||
power := exp(base, exponent)
|
||||
if gt(power, max) { <panic>() }
|
||||
leave
|
||||
}
|
||||
|
||||
power, base := <expLoop>(1, base, exponent, max)
|
||||
|
||||
if gt(power, div(max, base)) { <panic>() }
|
||||
power := mul(power, base)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("panic", panicFunction())
|
||||
("expLoop", overflowCheckedExpLoopFunction())
|
||||
("shr_1", shiftRightFunction(1))
|
||||
.render();
|
||||
});
|
||||
@ -692,8 +718,8 @@ string YulUtilFunctions::overflowCheckedSignedExpFunction()
|
||||
|
||||
// overflow check for base * base
|
||||
switch sgt(base, 0)
|
||||
case 1 { if gt(base, div(max, base)) { revert(0, 0) } }
|
||||
case 0 { if slt(base, sdiv(max, base)) { revert(0, 0) } }
|
||||
case 1 { if gt(base, div(max, base)) { <panic>() } }
|
||||
case 0 { if slt(base, sdiv(max, base)) { <panic>() } }
|
||||
if and(exponent, 1)
|
||||
{
|
||||
power := base
|
||||
@ -703,28 +729,56 @@ string YulUtilFunctions::overflowCheckedSignedExpFunction()
|
||||
|
||||
// Below this point, base is always positive.
|
||||
|
||||
power, base := <expLoop>(power, base, exponent, max)
|
||||
|
||||
if and(sgt(power, 0), gt(power, div(max, base))) { <panic>() }
|
||||
if and(slt(power, 0), slt(power, sdiv(min, base))) { <panic>() }
|
||||
power := mul(power, base)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("panic", panicFunction())
|
||||
("expLoop", overflowCheckedExpLoopFunction())
|
||||
("shr_1", shiftRightFunction(1))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::overflowCheckedExpLoopFunction()
|
||||
{
|
||||
// We use this loop for both signed and unsigned exponentiation
|
||||
// because we pull out the first iteration in the signed case which
|
||||
// results in the base always being positive.
|
||||
|
||||
// This function does not include the final multiplication.
|
||||
|
||||
string functionName = "checked_exp_helper";
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(_power, _base, exponent, max) -> power, base {
|
||||
power := _power
|
||||
base := _base
|
||||
for { } gt(exponent, 1) {}
|
||||
{
|
||||
// overflow check for base * base
|
||||
if gt(base, div(max, base)) { revert(0, 0) }
|
||||
if gt(base, div(max, base)) { <panic>() }
|
||||
if and(exponent, 1)
|
||||
{
|
||||
// No checks for power := mul(power, base) needed, because the check
|
||||
// for base * base above is sufficient, since:
|
||||
// |power| <= base (proof by induction) and thus:
|
||||
// |power * base| <= base * base <= max <= |min|
|
||||
// |power * base| <= base * base <= max <= |min| (for signed)
|
||||
// (this is equally true for signed and unsigned exp)
|
||||
power := mul(power, base)
|
||||
}
|
||||
base := mul(base, base)
|
||||
exponent := <shr_1>(exponent)
|
||||
}
|
||||
|
||||
if and(sgt(power, 0), gt(power, div(max, base))) { revert(0, 0) }
|
||||
if and(slt(power, 0), slt(power, sdiv(min, base))) { revert(0, 0) }
|
||||
power := mul(power, base)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("panic", panicFunction())
|
||||
("shr_1", shiftRightFunction(1))
|
||||
.render();
|
||||
});
|
||||
@ -804,7 +858,7 @@ std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(array, newLen) {
|
||||
if gt(newLen, <maxArrayLength>) {
|
||||
invalid()
|
||||
<panic>()
|
||||
}
|
||||
|
||||
let oldLen := <fetchLength>(array)
|
||||
@ -823,6 +877,7 @@ std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type)
|
||||
}
|
||||
})")
|
||||
("functionName", functionName)
|
||||
("panic", panicFunction())
|
||||
("fetchLength", arrayLengthFunction(_type))
|
||||
("convertToSize", arrayConvertLengthToSize(_type))
|
||||
("dataPosition", arrayDataAreaFunction(_type))
|
||||
@ -845,13 +900,14 @@ string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(array) {
|
||||
let oldLen := <fetchLength>(array)
|
||||
if iszero(oldLen) { invalid() }
|
||||
if iszero(oldLen) { <panic>() }
|
||||
let newLen := sub(oldLen, 1)
|
||||
let slot, offset := <indexAccess>(array, newLen)
|
||||
<setToZero>(slot, offset)
|
||||
sstore(array, newLen)
|
||||
})")
|
||||
("functionName", functionName)
|
||||
("panic", panicFunction())
|
||||
("fetchLength", arrayLengthFunction(_type))
|
||||
("indexAccess", storageArrayIndexAccessFunction(_type))
|
||||
("setToZero", storageSetToZeroFunction(*_type.baseType()))
|
||||
@ -871,7 +927,7 @@ string YulUtilFunctions::storageByteArrayPopFunction(ArrayType const& _type)
|
||||
function <functionName>(array) {
|
||||
let data := sload(array)
|
||||
let oldLen := <extractByteArrayLength>(data)
|
||||
if iszero(oldLen) { invalid() }
|
||||
if iszero(oldLen) { <panic>() }
|
||||
|
||||
switch eq(oldLen, 32)
|
||||
case 1 {
|
||||
@ -900,6 +956,7 @@ string YulUtilFunctions::storageByteArrayPopFunction(ArrayType const& _type)
|
||||
sstore(array, data)
|
||||
})")
|
||||
("functionName", functionName)
|
||||
("panic", panicFunction())
|
||||
("extractByteArrayLength", extractByteArrayLengthFunction())
|
||||
("dataAreaFunction", arrayDataAreaFunction(_type))
|
||||
("indexAccess", storageArrayIndexAccessFunction(_type))
|
||||
@ -922,7 +979,7 @@ string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
|
||||
<?isByteArray>
|
||||
let data := sload(array)
|
||||
let oldLen := <extractByteArrayLength>(data)
|
||||
if iszero(lt(oldLen, <maxArrayLength>)) { invalid() }
|
||||
if iszero(lt(oldLen, <maxArrayLength>)) { <panic>() }
|
||||
|
||||
switch gt(oldLen, 31)
|
||||
case 0 {
|
||||
@ -953,13 +1010,14 @@ string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
|
||||
}
|
||||
<!isByteArray>
|
||||
let oldLen := sload(array)
|
||||
if iszero(lt(oldLen, <maxArrayLength>)) { invalid() }
|
||||
if iszero(lt(oldLen, <maxArrayLength>)) { <panic>() }
|
||||
sstore(array, add(oldLen, 1))
|
||||
let slot, offset := <indexAccess>(array, oldLen)
|
||||
<storeValue>(slot, offset, value)
|
||||
</isByteArray>
|
||||
})")
|
||||
("functionName", functionName)
|
||||
("panic", panicFunction())
|
||||
("extractByteArrayLength", _type.isByteArray() ? extractByteArrayLengthFunction() : "")
|
||||
("dataAreaFunction", arrayDataAreaFunction(_type))
|
||||
("isByteArray", _type.isByteArray())
|
||||
@ -986,12 +1044,13 @@ string YulUtilFunctions::storageArrayPushZeroFunction(ArrayType const& _type)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(array) -> slot, offset {
|
||||
let oldLen := <fetchLength>(array)
|
||||
if iszero(lt(oldLen, <maxArrayLength>)) { invalid() }
|
||||
if iszero(lt(oldLen, <maxArrayLength>)) { <panic>() }
|
||||
sstore(array, add(oldLen, 1))
|
||||
slot, offset := <indexAccess>(array, oldLen)
|
||||
<storeValue>(slot, offset, <zeroValueFunction>())
|
||||
})")
|
||||
("functionName", functionName)
|
||||
("panic", panicFunction())
|
||||
("fetchLength", arrayLengthFunction(_type))
|
||||
("indexAccess", storageArrayIndexAccessFunction(_type))
|
||||
("storeValue", updateStorageValueFunction(*_type.baseType(), *_type.baseType()))
|
||||
@ -1126,7 +1185,7 @@ string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
|
||||
Whiskers w(R"(
|
||||
function <functionName>(length) -> size {
|
||||
// Make sure we can allocate memory without overflow
|
||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||
if gt(length, 0xffffffffffffffff) { <panic>() }
|
||||
<?byteArray>
|
||||
// round up
|
||||
size := and(add(length, 0x1f), not(0x1f))
|
||||
@ -1140,6 +1199,7 @@ string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
w("panic", panicFunction());
|
||||
w("byteArray", _type.isByteArray());
|
||||
w("dynamic", _type.isDynamicallySized());
|
||||
return w.render();
|
||||
@ -1184,7 +1244,7 @@ string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(array, index) -> slot, offset {
|
||||
let arrayLength := <arrayLen>(array)
|
||||
if iszero(lt(index, arrayLength)) { invalid() }
|
||||
if iszero(lt(index, arrayLength)) { <panic>() }
|
||||
|
||||
<?multipleItemsPerSlot>
|
||||
<?isBytesArray>
|
||||
@ -1198,10 +1258,9 @@ string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
|
||||
slot := array
|
||||
}
|
||||
<!isBytesArray>
|
||||
let itemsPerSlot := div(0x20, <storageBytes>)
|
||||
let dataArea := <dataAreaFunc>(array)
|
||||
slot := add(dataArea, div(index, itemsPerSlot))
|
||||
offset := mod(index, itemsPerSlot)
|
||||
slot := add(dataArea, div(index, <itemsPerSlot>))
|
||||
offset := mul(mod(index, <itemsPerSlot>), <storageBytes>)
|
||||
</isBytesArray>
|
||||
<!multipleItemsPerSlot>
|
||||
let dataArea := <dataAreaFunc>(array)
|
||||
@ -1211,12 +1270,14 @@ string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("panic", panicFunction())
|
||||
("arrayLen", arrayLengthFunction(_type))
|
||||
("dataAreaFunc", arrayDataAreaFunction(_type))
|
||||
("multipleItemsPerSlot", _type.baseType()->storageBytes() <= 16)
|
||||
("isBytesArray", _type.isByteArray())
|
||||
("storageSize", _type.baseType()->storageSize().str())
|
||||
("storageBytes", toString(_type.baseType()->storageBytes()))
|
||||
("itemsPerSlot", to_string(32 / _type.baseType()->storageBytes()))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
@ -1228,7 +1289,7 @@ string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(baseRef, index) -> addr {
|
||||
if iszero(lt(index, <arrayLen>(baseRef))) {
|
||||
invalid()
|
||||
<panic>()
|
||||
}
|
||||
|
||||
let offset := mul(index, <stride>)
|
||||
@ -1239,6 +1300,7 @@ string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("panic", panicFunction())
|
||||
("arrayLen", arrayLengthFunction(_type))
|
||||
("stride", to_string(_type.memoryStride()))
|
||||
("dynamicallySized", _type.isDynamicallySized())
|
||||
@ -1253,7 +1315,7 @@ string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& _type
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(base_ref<?dynamicallySized>, length</dynamicallySized>, index) -> addr<?dynamicallySizedBase>, len</dynamicallySizedBase> {
|
||||
if iszero(lt(index, <?dynamicallySized>length<!dynamicallySized><arrayLen></dynamicallySized>)) { invalid() }
|
||||
if iszero(lt(index, <?dynamicallySized>length<!dynamicallySized><arrayLen></dynamicallySized>)) { <panic>() }
|
||||
addr := add(base_ref, mul(index, <stride>))
|
||||
<?dynamicallyEncodedBase>
|
||||
addr<?dynamicallySizedBase>, len</dynamicallySizedBase> := <accessCalldataTail>(base_ref, addr)
|
||||
@ -1261,6 +1323,7 @@ string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& _type
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("panic", panicFunction())
|
||||
("stride", to_string(_type.calldataStride()))
|
||||
("dynamicallySized", _type.isDynamicallySized())
|
||||
("dynamicallyEncodedBase", _type.baseType()->isDynamicallyEncoded())
|
||||
@ -1806,12 +1869,13 @@ string YulUtilFunctions::allocationFunction()
|
||||
memPtr := mload(<freeMemoryPointer>)
|
||||
let newFreePtr := add(memPtr, size)
|
||||
// protect against overflow
|
||||
if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }
|
||||
if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { <panic>() }
|
||||
mstore(<freeMemoryPointer>, newFreePtr)
|
||||
}
|
||||
)")
|
||||
("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
|
||||
("functionName", functionName)
|
||||
("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
|
||||
("panic", panicFunction())
|
||||
.render();
|
||||
});
|
||||
}
|
||||
@ -2381,7 +2445,7 @@ string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFail
|
||||
if (_revertOnFailure)
|
||||
templ("failure", "revert(0, 0)");
|
||||
else
|
||||
templ("failure", "invalid()");
|
||||
templ("failure", panicFunction() + "()");
|
||||
|
||||
switch (_type.category())
|
||||
{
|
||||
@ -2492,11 +2556,12 @@ std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(value) -> ret {
|
||||
value := <cleanupFunction>(value)
|
||||
if <lt>(value, <minval>) { revert(0,0) }
|
||||
if <lt>(value, <minval>) { <panic>() }
|
||||
ret := sub(value, 1)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("panic", panicFunction())
|
||||
("minval", toCompactHexWithPrefix(minintval))
|
||||
("lt", type.isSigned() ? "slt" : "lt")
|
||||
("cleanupFunction", cleanupFunction(_type))
|
||||
@ -2522,13 +2587,14 @@ std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(value) -> ret {
|
||||
value := <cleanupFunction>(value)
|
||||
if <gt>(value, <maxval>) { revert(0,0) }
|
||||
if <gt>(value, <maxval>) { <panic>() }
|
||||
ret := add(value, 1)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("maxval", toCompactHexWithPrefix(maxintval))
|
||||
("gt", type.isSigned() ? "sgt" : "gt")
|
||||
("panic", panicFunction())
|
||||
("cleanupFunction", cleanupFunction(_type))
|
||||
.render();
|
||||
});
|
||||
@ -2547,13 +2613,14 @@ string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(value) -> ret {
|
||||
value := <cleanupFunction>(value)
|
||||
if slt(value, <minval>) { revert(0,0) }
|
||||
if slt(value, <minval>) { <panic>() }
|
||||
ret := sub(0, value)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("minval", toCompactHexWithPrefix(minintval))
|
||||
("cleanupFunction", cleanupFunction(_type))
|
||||
("panic", panicFunction())
|
||||
.render();
|
||||
});
|
||||
}
|
||||
@ -2889,6 +2956,20 @@ string YulUtilFunctions::revertReasonIfDebug(string const& _message)
|
||||
return revertReasonIfDebug(m_revertStrings, _message);
|
||||
}
|
||||
|
||||
string YulUtilFunctions::panicFunction()
|
||||
{
|
||||
string functionName = "panic_error";
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>() {
|
||||
invalid()
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::tryDecodeErrorMessageFunction()
|
||||
{
|
||||
string const functionName = "try_decode_error_message";
|
||||
|
@ -140,6 +140,10 @@ public:
|
||||
/// signature: (base, exponent, min, max) -> power
|
||||
std::string overflowCheckedSignedExpFunction();
|
||||
|
||||
/// Helper function for the two checked exponentiation functions.
|
||||
/// signature: (power, base, exponent, max) -> power
|
||||
std::string overflowCheckedExpLoopFunction();
|
||||
|
||||
/// @returns the name of a function that fetches the length of the given
|
||||
/// array
|
||||
/// signature: (array) -> length
|
||||
@ -369,6 +373,10 @@ public:
|
||||
|
||||
std::string revertReasonIfDebug(std::string const& _message = "");
|
||||
|
||||
/// Executes the invalid opcode.
|
||||
/// Might use revert with special error code in the future.
|
||||
std::string panicFunction();
|
||||
|
||||
/// Returns the name of a function that decodes an error message.
|
||||
/// signature: () -> arrayPtr
|
||||
///
|
||||
|
@ -142,6 +142,9 @@ public:
|
||||
|
||||
std::set<ContractDefinition const*, ASTNode::CompareByID>& subObjectsCreated() { return m_subObjects; }
|
||||
|
||||
bool inlineAssemblySeen() const { return m_inlineAssemblySeen; }
|
||||
void setInlineAssemblySeen() { m_inlineAssemblySeen = true; }
|
||||
|
||||
private:
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
RevertStrings m_revertStrings;
|
||||
@ -159,6 +162,9 @@ private:
|
||||
MultiUseYulFunctionCollector m_functions;
|
||||
size_t m_varCounter = 0;
|
||||
|
||||
/// Flag indicating whether any inline assembly block was seen.
|
||||
bool m_inlineAssemblySeen = false;
|
||||
|
||||
/// Function definitions queued for code generation. They're the Solidity functions whose calls
|
||||
/// were discovered by the IR generator during AST traversal.
|
||||
/// Note that the queue gets filled in a lazy way - new definitions can be added while the
|
||||
|
@ -92,7 +92,7 @@ string IRGenerator::generate(
|
||||
Whiskers t(R"(
|
||||
object "<CreationObject>" {
|
||||
code {
|
||||
<memoryInit>
|
||||
<memoryInitCreation>
|
||||
<callValueCheck>
|
||||
<?notLibrary>
|
||||
<?constructorHasParams> let <constructorParams> := <copyConstructorArguments>() </constructorHasParams>
|
||||
@ -103,7 +103,7 @@ string IRGenerator::generate(
|
||||
}
|
||||
object "<RuntimeObject>" {
|
||||
code {
|
||||
<memoryInit>
|
||||
<memoryInitRuntime>
|
||||
<dispatch>
|
||||
<runtimeFunctions>
|
||||
}
|
||||
@ -118,7 +118,6 @@ string IRGenerator::generate(
|
||||
m_context.registerImmutableVariable(*var);
|
||||
|
||||
t("CreationObject", IRNames::creationObject(_contract));
|
||||
t("memoryInit", memoryInit());
|
||||
t("notLibrary", !_contract.isLibrary());
|
||||
|
||||
FunctionDefinition const* constructor = _contract.constructor();
|
||||
@ -144,6 +143,10 @@ string IRGenerator::generate(
|
||||
t("functions", m_context.functionCollector().requestedFunctions());
|
||||
t("subObjects", subObjectSources(m_context.subObjectsCreated()));
|
||||
|
||||
// This has to be called only after all other code generation for the creation object is complete.
|
||||
bool creationInvolvesAssembly = m_context.inlineAssemblySeen();
|
||||
t("memoryInitCreation", memoryInit(!creationInvolvesAssembly));
|
||||
|
||||
resetContext(_contract);
|
||||
|
||||
// NOTE: Function pointers can be passed from creation code via storage variables. We need to
|
||||
@ -158,13 +161,17 @@ string IRGenerator::generate(
|
||||
generateInternalDispatchFunctions();
|
||||
t("runtimeFunctions", m_context.functionCollector().requestedFunctions());
|
||||
t("runtimeSubObjects", subObjectSources(m_context.subObjectsCreated()));
|
||||
|
||||
// This has to be called only after all other code generation for the runtime object is complete.
|
||||
bool runtimeInvolvesAssembly = m_context.inlineAssemblySeen();
|
||||
t("memoryInitRuntime", memoryInit(!runtimeInvolvesAssembly));
|
||||
return t.render();
|
||||
}
|
||||
|
||||
string IRGenerator::generate(Block const& _block)
|
||||
{
|
||||
IRGeneratorForStatements generator(m_context, m_utils);
|
||||
_block.accept(generator);
|
||||
generator.generate(_block);
|
||||
return generator.code();
|
||||
}
|
||||
|
||||
@ -197,10 +204,11 @@ InternalDispatchMap IRGenerator::generateInternalDispatchFunctions()
|
||||
<?+out> <out> :=</+out> <name>(<in>)
|
||||
}
|
||||
</cases>
|
||||
default { invalid() }
|
||||
default { <panic>() }
|
||||
}
|
||||
)");
|
||||
templ("functionName", funName);
|
||||
templ("panic", m_utils.panicFunction());
|
||||
templ("in", suffixedVariableNameList("in_", 0, arity.in));
|
||||
templ("out", suffixedVariableNameList("out_", 0, arity.out));
|
||||
|
||||
@ -651,16 +659,22 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
|
||||
return t.render();
|
||||
}
|
||||
|
||||
string IRGenerator::memoryInit()
|
||||
string IRGenerator::memoryInit(bool _useMemoryGuard)
|
||||
{
|
||||
// This function should be called at the beginning of the EVM call frame
|
||||
// and thus can assume all memory to be zero, including the contents of
|
||||
// the "zero memory area" (the position CompilerUtils::zeroPointer points to).
|
||||
return
|
||||
Whiskers{"mstore(<memPtr>, <freeMemoryStart>)"}
|
||||
Whiskers{
|
||||
_useMemoryGuard ?
|
||||
"mstore(<memPtr>, memoryguard(<freeMemoryStart>))" :
|
||||
"mstore(<memPtr>, <freeMemoryStart>)"
|
||||
}
|
||||
("memPtr", to_string(CompilerUtils::freeMemoryPointer))
|
||||
("freeMemoryStart", to_string(CompilerUtils::generalPurposeMemoryStart + m_context.reservedMemory()))
|
||||
.render();
|
||||
(
|
||||
"freeMemoryStart",
|
||||
to_string(CompilerUtils::generalPurposeMemoryStart + m_context.reservedMemory())
|
||||
).render();
|
||||
}
|
||||
|
||||
void IRGenerator::resetContext(ContractDefinition const& _contract)
|
||||
|
@ -100,7 +100,9 @@ private:
|
||||
|
||||
std::string dispatchRoutine(ContractDefinition const& _contract);
|
||||
|
||||
std::string memoryInit();
|
||||
/// @a _useMemoryGuard If true, use a memory guard, allowing the optimiser
|
||||
/// to perform memory optimizations.
|
||||
std::string memoryInit(bool _useMemoryGuard);
|
||||
|
||||
void resetContext(ContractDefinition const& _contract);
|
||||
|
||||
|
@ -38,6 +38,8 @@
|
||||
#include <libyul/Dialect.h>
|
||||
#include <libyul/optimiser/ASTCopier.h>
|
||||
|
||||
#include <liblangutil/Exceptions.h>
|
||||
|
||||
#include <libsolutil/Whiskers.h>
|
||||
#include <libsolutil/StringUtils.h>
|
||||
#include <libsolutil/Keccak256.h>
|
||||
@ -152,70 +154,130 @@ string IRGeneratorForStatements::code() const
|
||||
return m_code.str();
|
||||
}
|
||||
|
||||
void IRGeneratorForStatements::generate(Block const& _block)
|
||||
{
|
||||
try
|
||||
{
|
||||
_block.accept(*this);
|
||||
}
|
||||
catch (langutil::UnimplementedFeatureError const& _error)
|
||||
{
|
||||
if (!boost::get_error_info<langutil::errinfo_sourceLocation>(_error))
|
||||
_error << langutil::errinfo_sourceLocation(m_currentLocation);
|
||||
throw _error;
|
||||
}
|
||||
}
|
||||
|
||||
void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
solAssert(_varDecl.immutable() || m_context.isStateVariable(_varDecl), "Must be immutable or a state variable.");
|
||||
solAssert(!_varDecl.isConstant(), "");
|
||||
if (!_varDecl.value())
|
||||
return;
|
||||
try
|
||||
{
|
||||
setLocation(_varDecl);
|
||||
|
||||
_varDecl.value()->accept(*this);
|
||||
writeToLValue(
|
||||
_varDecl.immutable() ?
|
||||
IRLValue{*_varDecl.annotation().type, IRLValue::Immutable{&_varDecl}} :
|
||||
IRLValue{*_varDecl.annotation().type, IRLValue::Storage{
|
||||
util::toCompactHexWithPrefix(m_context.storageLocationOfStateVariable(_varDecl).first),
|
||||
m_context.storageLocationOfStateVariable(_varDecl).second
|
||||
}},
|
||||
*_varDecl.value()
|
||||
);
|
||||
solAssert(_varDecl.immutable() || m_context.isStateVariable(_varDecl), "Must be immutable or a state variable.");
|
||||
solAssert(!_varDecl.isConstant(), "");
|
||||
if (!_varDecl.value())
|
||||
return;
|
||||
|
||||
_varDecl.value()->accept(*this);
|
||||
writeToLValue(
|
||||
_varDecl.immutable() ?
|
||||
IRLValue{*_varDecl.annotation().type, IRLValue::Immutable{&_varDecl}} :
|
||||
IRLValue{*_varDecl.annotation().type, IRLValue::Storage{
|
||||
util::toCompactHexWithPrefix(m_context.storageLocationOfStateVariable(_varDecl).first),
|
||||
m_context.storageLocationOfStateVariable(_varDecl).second
|
||||
}},
|
||||
*_varDecl.value()
|
||||
);
|
||||
}
|
||||
catch (langutil::UnimplementedFeatureError const& _error)
|
||||
{
|
||||
if (!boost::get_error_info<langutil::errinfo_sourceLocation>(_error))
|
||||
_error << langutil::errinfo_sourceLocation(m_currentLocation);
|
||||
throw _error;
|
||||
}
|
||||
}
|
||||
|
||||
void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
solAssert(m_context.isLocalVariable(_varDecl), "Must be a local variable.");
|
||||
try
|
||||
{
|
||||
setLocation(_varDecl);
|
||||
|
||||
auto const* type = _varDecl.type();
|
||||
if (auto const* refType = dynamic_cast<ReferenceType const*>(type))
|
||||
if (refType->dataStoredIn(DataLocation::Storage) && refType->isPointer())
|
||||
return;
|
||||
solAssert(m_context.isLocalVariable(_varDecl), "Must be a local variable.");
|
||||
|
||||
IRVariable zero = zeroValue(*type);
|
||||
assign(m_context.localVariable(_varDecl), zero);
|
||||
auto const* type = _varDecl.type();
|
||||
if (auto const* refType = dynamic_cast<ReferenceType const*>(type))
|
||||
if (refType->dataStoredIn(DataLocation::Storage) && refType->isPointer())
|
||||
return;
|
||||
|
||||
IRVariable zero = zeroValue(*type);
|
||||
assign(m_context.localVariable(_varDecl), zero);
|
||||
}
|
||||
catch (langutil::UnimplementedFeatureError const& _error)
|
||||
{
|
||||
if (!boost::get_error_info<langutil::errinfo_sourceLocation>(_error))
|
||||
_error << langutil::errinfo_sourceLocation(m_currentLocation);
|
||||
throw _error;
|
||||
}
|
||||
}
|
||||
|
||||
IRVariable IRGeneratorForStatements::evaluateExpression(Expression const& _expression, Type const& _targetType)
|
||||
{
|
||||
_expression.accept(*this);
|
||||
IRVariable variable{m_context.newYulVariable(), _targetType};
|
||||
define(variable, _expression);
|
||||
return variable;
|
||||
try
|
||||
{
|
||||
setLocation(_expression);
|
||||
|
||||
_expression.accept(*this);
|
||||
IRVariable variable{m_context.newYulVariable(), _targetType};
|
||||
define(variable, _expression);
|
||||
return variable;
|
||||
}
|
||||
catch (langutil::UnimplementedFeatureError const& _error)
|
||||
{
|
||||
if (!boost::get_error_info<langutil::errinfo_sourceLocation>(_error))
|
||||
_error << langutil::errinfo_sourceLocation(m_currentLocation);
|
||||
throw _error;
|
||||
}
|
||||
}
|
||||
|
||||
string IRGeneratorForStatements::constantValueFunction(VariableDeclaration const& _constant)
|
||||
{
|
||||
string functionName = IRNames::constantValueFunction(_constant);
|
||||
return m_context.functionCollector().createFunction(functionName, [&] {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>() -> <ret> {
|
||||
<code>
|
||||
<ret> := <value>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
IRGeneratorForStatements generator(m_context, m_utils);
|
||||
solAssert(_constant.value(), "");
|
||||
Type const& constantType = *_constant.type();
|
||||
templ("value", generator.evaluateExpression(*_constant.value(), constantType).commaSeparatedList());
|
||||
templ("code", generator.code());
|
||||
templ("ret", IRVariable("ret", constantType).commaSeparatedList());
|
||||
try
|
||||
{
|
||||
setLocation(_constant);
|
||||
|
||||
return templ.render();
|
||||
});
|
||||
string functionName = IRNames::constantValueFunction(_constant);
|
||||
return m_context.functionCollector().createFunction(functionName, [&] {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>() -> <ret> {
|
||||
<code>
|
||||
<ret> := <value>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
IRGeneratorForStatements generator(m_context, m_utils);
|
||||
solAssert(_constant.value(), "");
|
||||
Type const& constantType = *_constant.type();
|
||||
templ("value", generator.evaluateExpression(*_constant.value(), constantType).commaSeparatedList());
|
||||
templ("code", generator.code());
|
||||
templ("ret", IRVariable("ret", constantType).commaSeparatedList());
|
||||
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
catch (langutil::UnimplementedFeatureError const& _error)
|
||||
{
|
||||
if (!boost::get_error_info<langutil::errinfo_sourceLocation>(_error))
|
||||
_error << langutil::errinfo_sourceLocation(m_currentLocation);
|
||||
throw _error;
|
||||
}
|
||||
}
|
||||
|
||||
void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement)
|
||||
{
|
||||
setLocation(_varDeclStatement);
|
||||
|
||||
if (Expression const* expression = _varDeclStatement.initialValue())
|
||||
{
|
||||
if (_varDeclStatement.declarations().size() > 1)
|
||||
@ -249,14 +311,22 @@ bool IRGeneratorForStatements::visit(Conditional const& _conditional)
|
||||
{
|
||||
_conditional.condition().accept(*this);
|
||||
|
||||
setLocation(_conditional);
|
||||
|
||||
string condition = expressionAsType(_conditional.condition(), *TypeProvider::boolean());
|
||||
declare(_conditional);
|
||||
|
||||
m_code << "switch " << condition << "\n" "case 0 {\n";
|
||||
|
||||
_conditional.falseExpression().accept(*this);
|
||||
setLocation(_conditional);
|
||||
|
||||
assign(_conditional, _conditional.falseExpression());
|
||||
m_code << "}\n" "default {\n";
|
||||
|
||||
_conditional.trueExpression().accept(*this);
|
||||
setLocation(_conditional);
|
||||
|
||||
assign(_conditional, _conditional.trueExpression());
|
||||
m_code << "}\n";
|
||||
|
||||
@ -266,6 +336,7 @@ bool IRGeneratorForStatements::visit(Conditional const& _conditional)
|
||||
bool IRGeneratorForStatements::visit(Assignment const& _assignment)
|
||||
{
|
||||
_assignment.rightHandSide().accept(*this);
|
||||
setLocation(_assignment);
|
||||
|
||||
Token assignmentOperator = _assignment.assignmentOperator();
|
||||
Token binaryOperator =
|
||||
@ -283,6 +354,7 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment)
|
||||
IRVariable value = convert(_assignment.rightHandSide(), *rightIntermediateType);
|
||||
_assignment.leftHandSide().accept(*this);
|
||||
solAssert(!!m_currentLValue, "LValue not retrieved.");
|
||||
setLocation(_assignment);
|
||||
|
||||
if (assignmentOperator != Token::Assign)
|
||||
{
|
||||
@ -323,6 +395,8 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment)
|
||||
|
||||
bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
|
||||
{
|
||||
setLocation(_tuple);
|
||||
|
||||
if (_tuple.isInlineArray())
|
||||
{
|
||||
auto const& arrayType = dynamic_cast<ArrayType const&>(*_tuple.annotation().type);
|
||||
@ -339,6 +413,7 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
|
||||
{
|
||||
Expression const& component = *_tuple.components()[i];
|
||||
component.accept(*this);
|
||||
setLocation(_tuple);
|
||||
IRVariable converted = convert(component, baseType);
|
||||
m_code <<
|
||||
m_utils.writeToMemoryFunction(baseType) <<
|
||||
@ -358,6 +433,7 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
|
||||
{
|
||||
solAssert(_tuple.components().front(), "");
|
||||
_tuple.components().front()->accept(*this);
|
||||
setLocation(_tuple);
|
||||
if (willBeWrittenTo)
|
||||
solAssert(!!m_currentLValue, "");
|
||||
else
|
||||
@ -370,6 +446,7 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
|
||||
if (auto const& component = _tuple.components()[i])
|
||||
{
|
||||
component->accept(*this);
|
||||
setLocation(_tuple);
|
||||
if (willBeWrittenTo)
|
||||
{
|
||||
solAssert(!!m_currentLValue, "");
|
||||
@ -395,17 +472,20 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
|
||||
bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement)
|
||||
{
|
||||
_ifStatement.condition().accept(*this);
|
||||
setLocation(_ifStatement);
|
||||
string condition = expressionAsType(_ifStatement.condition(), *TypeProvider::boolean());
|
||||
|
||||
if (_ifStatement.falseStatement())
|
||||
{
|
||||
m_code << "switch " << condition << "\n" "case 0 {\n";
|
||||
_ifStatement.falseStatement()->accept(*this);
|
||||
setLocation(_ifStatement);
|
||||
m_code << "}\n" "default {\n";
|
||||
}
|
||||
else
|
||||
m_code << "if " << condition << " {\n";
|
||||
_ifStatement.trueStatement().accept(*this);
|
||||
setLocation(_ifStatement);
|
||||
m_code << "}\n";
|
||||
|
||||
return false;
|
||||
@ -413,6 +493,7 @@ bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement)
|
||||
|
||||
bool IRGeneratorForStatements::visit(ForStatement const& _forStatement)
|
||||
{
|
||||
setLocation(_forStatement);
|
||||
generateLoop(
|
||||
_forStatement.body(),
|
||||
_forStatement.condition(),
|
||||
@ -425,6 +506,7 @@ bool IRGeneratorForStatements::visit(ForStatement const& _forStatement)
|
||||
|
||||
bool IRGeneratorForStatements::visit(WhileStatement const& _whileStatement)
|
||||
{
|
||||
setLocation(_whileStatement);
|
||||
generateLoop(
|
||||
_whileStatement.body(),
|
||||
&_whileStatement.condition(),
|
||||
@ -436,20 +518,23 @@ bool IRGeneratorForStatements::visit(WhileStatement const& _whileStatement)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IRGeneratorForStatements::visit(Continue const&)
|
||||
bool IRGeneratorForStatements::visit(Continue const& _continue)
|
||||
{
|
||||
setLocation(_continue);
|
||||
m_code << "continue\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IRGeneratorForStatements::visit(Break const&)
|
||||
bool IRGeneratorForStatements::visit(Break const& _break)
|
||||
{
|
||||
setLocation(_break);
|
||||
m_code << "break\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
void IRGeneratorForStatements::endVisit(Return const& _return)
|
||||
{
|
||||
setLocation(_return);
|
||||
if (Expression const* value = _return.expression())
|
||||
{
|
||||
solAssert(_return.annotation().functionReturnParameters, "Invalid return parameters pointer.");
|
||||
@ -466,6 +551,7 @@ void IRGeneratorForStatements::endVisit(Return const& _return)
|
||||
|
||||
void IRGeneratorForStatements::endVisit(UnaryOperation const& _unaryOperation)
|
||||
{
|
||||
setLocation(_unaryOperation);
|
||||
Type const& resultType = type(_unaryOperation);
|
||||
Token const op = _unaryOperation.getOperator();
|
||||
|
||||
@ -551,6 +637,8 @@ void IRGeneratorForStatements::endVisit(UnaryOperation const& _unaryOperation)
|
||||
|
||||
bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
|
||||
{
|
||||
setLocation(_binOp);
|
||||
|
||||
solAssert(!!_binOp.annotation().commonType, "");
|
||||
TypePointer commonType = _binOp.annotation().commonType;
|
||||
langutil::Token op = _binOp.getOperator();
|
||||
@ -570,6 +658,7 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
|
||||
|
||||
_binOp.leftExpression().accept(*this);
|
||||
_binOp.rightExpression().accept(*this);
|
||||
setLocation(_binOp);
|
||||
|
||||
if (TokenTraits::isCompareOp(op))
|
||||
{
|
||||
@ -629,6 +718,7 @@ bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
|
||||
|
||||
bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall)
|
||||
{
|
||||
setLocation(_functionCall);
|
||||
FunctionTypePointer functionType = dynamic_cast<FunctionType const*>(&type(_functionCall.expression()));
|
||||
if (
|
||||
functionType &&
|
||||
@ -643,6 +733,7 @@ bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall)
|
||||
|
||||
void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
{
|
||||
setLocation(_functionCall);
|
||||
auto functionCallKind = *_functionCall.annotation().kind;
|
||||
|
||||
if (functionCallKind == FunctionCallKind::TypeConversion)
|
||||
@ -1156,8 +1247,10 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
|
||||
IRVariable modulus(m_context.newYulVariable(), *(parameterTypes[2]));
|
||||
define(modulus, *arguments[2]);
|
||||
Whiskers templ("if iszero(<modulus>) { invalid() }\n");
|
||||
m_code << templ("modulus", modulus.name()).render();
|
||||
Whiskers templ("if iszero(<modulus>) { <panic>() }\n");
|
||||
templ("modulus", modulus.name());
|
||||
templ("panic", m_utils.panicFunction());
|
||||
m_code << templ.render();
|
||||
|
||||
string args;
|
||||
for (size_t i = 0; i < 2; ++i)
|
||||
@ -1234,7 +1327,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
Whiskers t(R"(
|
||||
let <memPos> := <allocateTemporaryMemory>()
|
||||
let <memEnd> := add(<memPos>, datasize("<object>"))
|
||||
if or(gt(<memEnd>, 0xffffffffffffffff), lt(<memEnd>, <memPos>)) { revert(0, 0) }
|
||||
if or(gt(<memEnd>, 0xffffffffffffffff), lt(<memEnd>, <memPos>)) { <panic>() }
|
||||
datacopy(<memPos>, dataoffset("<object>"), datasize("<object>"))
|
||||
<memEnd> := <abiEncode>(<memEnd><constructorParams>)
|
||||
<?saltSet>
|
||||
@ -1249,6 +1342,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
t("allocateTemporaryMemory", m_utils.allocationTemporaryMemoryFunction());
|
||||
t("releaseTemporaryMemory", m_utils.releaseTemporaryMemoryFunction());
|
||||
t("object", IRNames::creationObject(*contract));
|
||||
t("panic", m_utils.panicFunction());
|
||||
t("abiEncode",
|
||||
m_context.abiFunctions().tupleEncoder(argumentTypes, functionType->parameterTypes(), false)
|
||||
);
|
||||
@ -1360,6 +1454,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
|
||||
void IRGeneratorForStatements::endVisit(FunctionCallOptions const& _options)
|
||||
{
|
||||
setLocation(_options);
|
||||
FunctionType const& previousType = dynamic_cast<FunctionType const&>(*_options.expression().annotation().type);
|
||||
|
||||
solUnimplementedAssert(!previousType.bound(), "");
|
||||
@ -1379,6 +1474,7 @@ void IRGeneratorForStatements::endVisit(FunctionCallOptions const& _options)
|
||||
|
||||
void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
||||
{
|
||||
setLocation(_memberAccess);
|
||||
ASTString const& member = _memberAccess.memberName();
|
||||
auto memberFunctionType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type);
|
||||
Type::Category objectCategory = _memberAccess.expression().annotation().type->category();
|
||||
@ -1549,10 +1645,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
||||
{
|
||||
TypePointer arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
|
||||
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition();
|
||||
uint64_t result{0};
|
||||
for (auto const& function: contract.interfaceFunctionList(false))
|
||||
result ^= fromBigEndian<uint64_t>(function.first.ref());
|
||||
define(_memberAccess) << formatNumber(u256{result} << (256 - 32)) << "\n";
|
||||
define(_memberAccess) << formatNumber(u256{contract.interfaceId()} << (256 - 32)) << "\n";
|
||||
}
|
||||
else if (member == "min" || member == "max")
|
||||
{
|
||||
@ -1767,6 +1860,8 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
||||
|
||||
bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm)
|
||||
{
|
||||
setLocation(_inlineAsm);
|
||||
m_context.setInlineAssemblySeen();
|
||||
CopyTranslate bodyCopier{_inlineAsm.dialect(), m_context, _inlineAsm.annotation().externalReferences};
|
||||
|
||||
yul::Statement modified = bodyCopier(_inlineAsm.operations());
|
||||
@ -1781,6 +1876,7 @@ bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm)
|
||||
|
||||
void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
|
||||
{
|
||||
setLocation(_indexAccess);
|
||||
Type const& baseType = *_indexAccess.baseExpression().annotation().type;
|
||||
|
||||
if (baseType.category() == Type::Category::Mapping)
|
||||
@ -1894,11 +1990,12 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
|
||||
IRVariable index{m_context.newYulVariable(), *TypeProvider::uint256()};
|
||||
define(index, *_indexAccess.indexExpression());
|
||||
m_code << Whiskers(R"(
|
||||
if iszero(lt(<index>, <length>)) { invalid() }
|
||||
if iszero(lt(<index>, <length>)) { <panic>() }
|
||||
let <result> := <shl248>(byte(<index>, <array>))
|
||||
)")
|
||||
("index", index.name())
|
||||
("length", to_string(fixedBytesType.numBytes()))
|
||||
("panic", m_utils.panicFunction())
|
||||
("array", IRVariable(_indexAccess.baseExpression()).name())
|
||||
("shl248", m_utils.shiftLeftFunction(256 - 8))
|
||||
("result", IRVariable(_indexAccess).name())
|
||||
@ -1916,6 +2013,7 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
|
||||
|
||||
void IRGeneratorForStatements::endVisit(IndexRangeAccess const& _indexRangeAccess)
|
||||
{
|
||||
setLocation(_indexRangeAccess);
|
||||
Type const& baseType = *_indexRangeAccess.baseExpression().annotation().type;
|
||||
solAssert(
|
||||
baseType.category() == Type::Category::Array || baseType.category() == Type::Category::ArraySlice,
|
||||
@ -1962,6 +2060,7 @@ void IRGeneratorForStatements::endVisit(IndexRangeAccess const& _indexRangeAcces
|
||||
|
||||
void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
|
||||
{
|
||||
setLocation(_identifier);
|
||||
Declaration const* declaration = _identifier.annotation().referencedDeclaration;
|
||||
if (MagicVariableDeclaration const* magicVar = dynamic_cast<MagicVariableDeclaration const*>(declaration))
|
||||
{
|
||||
@ -2020,6 +2119,7 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
|
||||
|
||||
bool IRGeneratorForStatements::visit(Literal const& _literal)
|
||||
{
|
||||
setLocation(_literal);
|
||||
Type const& literalType = type(_literal);
|
||||
|
||||
switch (literalType.category())
|
||||
@ -2042,6 +2142,7 @@ void IRGeneratorForStatements::handleVariableReference(
|
||||
Expression const& _referencingExpression
|
||||
)
|
||||
{
|
||||
setLocation(_referencingExpression);
|
||||
if (_variable.isStateVariable() && _variable.isConstant())
|
||||
define(_referencingExpression) << constantValueFunction(_variable) << "()\n";
|
||||
else if (_variable.isStateVariable() && _variable.immutable())
|
||||
@ -2483,6 +2584,7 @@ void IRGeneratorForStatements::appendAndOrOperatorCode(BinaryOperation const& _b
|
||||
solAssert(op == Token::Or || op == Token::And, "");
|
||||
|
||||
_binOp.leftExpression().accept(*this);
|
||||
setLocation(_binOp);
|
||||
|
||||
IRVariable value(_binOp);
|
||||
define(value, _binOp.leftExpression());
|
||||
@ -2491,6 +2593,7 @@ void IRGeneratorForStatements::appendAndOrOperatorCode(BinaryOperation const& _b
|
||||
else
|
||||
m_code << "if " << value.name() << " {\n";
|
||||
_binOp.rightExpression().accept(*this);
|
||||
setLocation(_binOp);
|
||||
assign(value, _binOp.rightExpression());
|
||||
m_code << "}\n";
|
||||
}
|
||||
@ -2690,6 +2793,7 @@ bool IRGeneratorForStatements::visit(TryStatement const& _tryStatement)
|
||||
{
|
||||
Expression const& externalCall = _tryStatement.externalCall();
|
||||
externalCall.accept(*this);
|
||||
setLocation(_tryStatement);
|
||||
|
||||
m_code << "switch iszero(" << IRNames::trySuccessConditionVariable(externalCall) << ")\n";
|
||||
|
||||
@ -2710,6 +2814,7 @@ bool IRGeneratorForStatements::visit(TryStatement const& _tryStatement)
|
||||
}
|
||||
|
||||
successClause.block().accept(*this);
|
||||
setLocation(_tryStatement);
|
||||
m_code << "}\n";
|
||||
|
||||
m_code << "default { // failure case\n";
|
||||
@ -2800,3 +2905,8 @@ bool IRGeneratorForStatements::visit(TryCatchClause const& _clause)
|
||||
_clause.block().accept(*this);
|
||||
return false;
|
||||
}
|
||||
|
||||
void IRGeneratorForStatements::setLocation(ASTNode const& _node)
|
||||
{
|
||||
m_currentLocation = _node.location();
|
||||
}
|
||||
|
@ -47,6 +47,9 @@ public:
|
||||
|
||||
std::string code() const;
|
||||
|
||||
/// Generate the code for the statements in the block;
|
||||
void generate(Block const& _block);
|
||||
|
||||
/// Generates code to initialize the given state variable.
|
||||
void initializeStateVar(VariableDeclaration const& _varDecl);
|
||||
/// Generates code to initialize the given local variable.
|
||||
@ -179,10 +182,13 @@ private:
|
||||
|
||||
static Type const& type(Expression const& _expression);
|
||||
|
||||
void setLocation(ASTNode const& _node);
|
||||
|
||||
std::ostringstream m_code;
|
||||
IRGenerationContext& m_context;
|
||||
YulUtilFunctions& m_utils;
|
||||
std::optional<IRLValue> m_currentLValue;
|
||||
langutil::SourceLocation m_currentLocation;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -580,7 +580,7 @@ pair<vector<smtutil::Expression>, vector<string>> BMC::modelExpressions()
|
||||
auto const& type = var.second->type();
|
||||
if (
|
||||
type->isValueType() &&
|
||||
smt::smtKind(type->category()) != smtutil::Kind::Function
|
||||
smt::smtKind(*type) != smtutil::Kind::Function
|
||||
)
|
||||
{
|
||||
expressionsToEvaluate.emplace_back(var.second->currentValue());
|
||||
@ -836,11 +836,11 @@ void BMC::checkCondition(
|
||||
case smtutil::CheckResult::SATISFIABLE:
|
||||
{
|
||||
std::ostringstream message;
|
||||
message << _description << " happens here";
|
||||
message << "BMC: " << _description << " happens here.";
|
||||
if (_callStack.size())
|
||||
{
|
||||
std::ostringstream modelMessage;
|
||||
modelMessage << " for:\n";
|
||||
modelMessage << "\nCounterexample:\n";
|
||||
solAssert(values.size() == expressionNames.size(), "");
|
||||
map<string, string> sortedModel;
|
||||
for (size_t i = 0; i < values.size(); ++i)
|
||||
@ -859,22 +859,19 @@ void BMC::checkCondition(
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
message << ".";
|
||||
m_errorReporter.warning(6084_error, _location, message.str(), secondaryLocation);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case smtutil::CheckResult::UNSATISFIABLE:
|
||||
break;
|
||||
case smtutil::CheckResult::UNKNOWN:
|
||||
m_errorReporter.warning(_errorMightHappen, _location, _description + " might happen here.", secondaryLocation);
|
||||
m_errorReporter.warning(_errorMightHappen, _location, "BMC: " + _description + " might happen here.", secondaryLocation);
|
||||
break;
|
||||
case smtutil::CheckResult::CONFLICTING:
|
||||
m_errorReporter.warning(1584_error, _location, "At least two SMT solvers provided conflicting answers. Results might not be sound.");
|
||||
m_errorReporter.warning(1584_error, _location, "BMC: At least two SMT solvers provided conflicting answers. Results might not be sound.");
|
||||
break;
|
||||
case smtutil::CheckResult::ERROR:
|
||||
m_errorReporter.warning(1823_error, _location, "Error trying to invoke SMT solver.");
|
||||
m_errorReporter.warning(1823_error, _location, "BMC: Error trying to invoke SMT solver.");
|
||||
break;
|
||||
}
|
||||
|
||||
@ -922,13 +919,13 @@ void BMC::checkBooleanNotConstant(
|
||||
if (positiveResult == smtutil::CheckResult::SATISFIABLE)
|
||||
{
|
||||
solAssert(negatedResult == smtutil::CheckResult::UNSATISFIABLE, "");
|
||||
description = "Condition is always true.";
|
||||
description = "BMC: Condition is always true.";
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(positiveResult == smtutil::CheckResult::UNSATISFIABLE, "");
|
||||
solAssert(negatedResult == smtutil::CheckResult::SATISFIABLE, "");
|
||||
description = "Condition is always false.";
|
||||
description = "BMC: Condition is always false.";
|
||||
}
|
||||
m_errorReporter.warning(
|
||||
6838_error,
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <libsmtutil/Z3CHCInterface.h>
|
||||
#endif
|
||||
|
||||
#include <libsolidity/formal/PredicateSort.h>
|
||||
#include <libsolidity/formal/SymbolicTypes.h>
|
||||
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
@ -39,54 +40,33 @@ using namespace solidity::util;
|
||||
using namespace solidity::langutil;
|
||||
using namespace solidity::smtutil;
|
||||
using namespace solidity::frontend;
|
||||
using namespace solidity::frontend::smt;
|
||||
|
||||
CHC::CHC(
|
||||
smt::EncodingContext& _context,
|
||||
EncodingContext& _context,
|
||||
ErrorReporter& _errorReporter,
|
||||
map<util::h256, string> const& _smtlib2Responses,
|
||||
ReadCallback::Callback const& _smtCallback,
|
||||
[[maybe_unused]] smtutil::SMTSolverChoice _enabledSolvers
|
||||
[[maybe_unused]] map<util::h256, string> const& _smtlib2Responses,
|
||||
[[maybe_unused]] ReadCallback::Callback const& _smtCallback,
|
||||
SMTSolverChoice _enabledSolvers
|
||||
):
|
||||
SMTEncoder(_context),
|
||||
m_outerErrorReporter(_errorReporter),
|
||||
m_enabledSolvers(_enabledSolvers)
|
||||
{
|
||||
#ifdef HAVE_Z3
|
||||
if (_enabledSolvers.z3)
|
||||
m_interface = make_unique<smtutil::Z3CHCInterface>();
|
||||
bool usesZ3 = _enabledSolvers.z3;
|
||||
#ifndef HAVE_Z3
|
||||
usesZ3 = false;
|
||||
#endif
|
||||
if (!m_interface)
|
||||
m_interface = make_unique<smtutil::CHCSmtLib2Interface>(_smtlib2Responses, _smtCallback);
|
||||
if (!usesZ3)
|
||||
m_interface = make_unique<CHCSmtLib2Interface>(_smtlib2Responses, _smtCallback);
|
||||
}
|
||||
|
||||
void CHC::analyze(SourceUnit const& _source)
|
||||
{
|
||||
solAssert(_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker), "");
|
||||
|
||||
bool usesZ3 = false;
|
||||
#ifdef HAVE_Z3
|
||||
usesZ3 = m_enabledSolvers.z3;
|
||||
if (usesZ3)
|
||||
{
|
||||
auto z3Interface = dynamic_cast<smtutil::Z3CHCInterface const*>(m_interface.get());
|
||||
solAssert(z3Interface, "");
|
||||
m_context.setSolver(z3Interface->z3Interface());
|
||||
}
|
||||
#endif
|
||||
if (!usesZ3)
|
||||
{
|
||||
auto smtlib2Interface = dynamic_cast<smtutil::CHCSmtLib2Interface const*>(m_interface.get());
|
||||
solAssert(smtlib2Interface, "");
|
||||
m_context.setSolver(smtlib2Interface->smtlib2Interface());
|
||||
}
|
||||
m_context.clear();
|
||||
m_context.setAssertionAccumulation(false);
|
||||
|
||||
resetSourceAnalysis();
|
||||
|
||||
m_genesisPredicate = createSymbolicBlock(arity0FunctionSort(), "genesis");
|
||||
addRule(genesis(), "genesis");
|
||||
|
||||
set<SourceUnit const*, IdCompare> sources;
|
||||
sources.insert(&_source);
|
||||
for (auto const& source: _source.referencedSourceUnits(true))
|
||||
@ -101,7 +81,7 @@ void CHC::analyze(SourceUnit const& _source)
|
||||
|
||||
vector<string> CHC::unhandledQueries() const
|
||||
{
|
||||
if (auto smtlib2 = dynamic_cast<smtutil::CHCSmtLib2Interface const*>(m_interface.get()))
|
||||
if (auto smtlib2 = dynamic_cast<CHCSmtLib2Interface const*>(m_interface.get()))
|
||||
return smtlib2->unhandledQueries();
|
||||
|
||||
return {};
|
||||
@ -114,13 +94,15 @@ bool CHC::visit(ContractDefinition const& _contract)
|
||||
initContract(_contract);
|
||||
|
||||
m_stateVariables = SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract);
|
||||
m_stateSorts = stateSorts(_contract);
|
||||
|
||||
clearIndices(&_contract);
|
||||
|
||||
string suffix = _contract.name() + "_" + to_string(_contract.id());
|
||||
m_constructorSummaryPredicate = createSymbolicBlock(constructorSort(), "summary_constructor_" + suffix, &_contract);
|
||||
m_implicitConstructorPredicate = createSymbolicBlock(arity0FunctionSort(), "implicit_constructor_" + suffix, &_contract);
|
||||
solAssert(m_currentContract, "");
|
||||
m_constructorSummaryPredicate = createSymbolicBlock(
|
||||
constructorSort(*m_currentContract),
|
||||
"summary_constructor_" + contractSuffix(_contract),
|
||||
&_contract
|
||||
);
|
||||
auto stateExprs = currentStateVariables();
|
||||
setCurrentBlock(*m_interfaces.at(m_currentContract), &stateExprs);
|
||||
|
||||
@ -130,8 +112,13 @@ bool CHC::visit(ContractDefinition const& _contract)
|
||||
|
||||
void CHC::endVisit(ContractDefinition const& _contract)
|
||||
{
|
||||
auto implicitConstructor = (*m_implicitConstructorPredicate)({});
|
||||
connectBlocks(genesis(), implicitConstructor);
|
||||
auto implicitConstructorPredicate = createSymbolicBlock(
|
||||
implicitConstructorSort(),
|
||||
"implicit_constructor_" + contractSuffix(_contract),
|
||||
&_contract
|
||||
);
|
||||
auto implicitConstructor = (*implicitConstructorPredicate)({});
|
||||
addRule(implicitConstructor, implicitConstructor.name);
|
||||
m_currentBlock = implicitConstructor;
|
||||
m_context.addAssertion(m_error.currentValue() == 0);
|
||||
|
||||
@ -156,7 +143,7 @@ bool CHC::visit(FunctionDefinition const& _function)
|
||||
{
|
||||
if (!_function.isImplemented())
|
||||
{
|
||||
connectBlocks(genesis(), summary(_function));
|
||||
addRule(summary(_function), "summary_function_" + to_string(_function.id()));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -184,7 +171,7 @@ bool CHC::visit(FunctionDefinition const& _function)
|
||||
if (_function.isConstructor())
|
||||
connectBlocks(m_currentBlock, functionPred);
|
||||
else
|
||||
connectBlocks(genesis(), functionPred);
|
||||
addRule(functionPred, functionPred.name);
|
||||
|
||||
m_context.addAssertion(m_error.currentValue() == 0);
|
||||
for (auto const* var: m_stateVariables)
|
||||
@ -226,7 +213,12 @@ void CHC::endVisit(FunctionDefinition const& _function)
|
||||
if (_function.isConstructor())
|
||||
{
|
||||
string suffix = m_currentContract->name() + "_" + to_string(m_currentContract->id());
|
||||
auto constructorExit = createSymbolicBlock(constructorSort(), "constructor_exit_" + suffix, m_currentContract);
|
||||
solAssert(m_currentContract, "");
|
||||
auto constructorExit = createSymbolicBlock(
|
||||
constructorSort(*m_currentContract),
|
||||
"constructor_exit_" + suffix,
|
||||
m_currentContract
|
||||
);
|
||||
connectBlocks(m_currentBlock, predicate(*constructorExit, currentFunctionVariables(*m_currentContract)));
|
||||
|
||||
clearIndices(m_currentContract, m_currentFunction);
|
||||
@ -588,7 +580,7 @@ void CHC::makeArrayPopVerificationTarget(FunctionCall const& _arrayPop)
|
||||
|
||||
auto memberAccess = dynamic_cast<MemberAccess const*>(&_arrayPop.expression());
|
||||
solAssert(memberAccess, "");
|
||||
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(memberAccess->expression()));
|
||||
auto symbArray = dynamic_pointer_cast<SymbolicArrayVariable>(m_context.expression(memberAccess->expression()));
|
||||
solAssert(symbArray, "");
|
||||
|
||||
auto previousError = m_error.currentValue();
|
||||
@ -676,11 +668,34 @@ void CHC::resetSourceAnalysis()
|
||||
m_interfaces.clear();
|
||||
m_nondetInterfaces.clear();
|
||||
Predicate::reset();
|
||||
m_blockCounter = 0;
|
||||
|
||||
bool usesZ3 = false;
|
||||
#ifdef HAVE_Z3
|
||||
usesZ3 = m_enabledSolvers.z3;
|
||||
if (usesZ3)
|
||||
{
|
||||
/// z3::fixedpoint does not have a reset mechanism, so we need to create another.
|
||||
m_interface.reset(new Z3CHCInterface());
|
||||
auto z3Interface = dynamic_cast<Z3CHCInterface const*>(m_interface.get());
|
||||
solAssert(z3Interface, "");
|
||||
m_context.setSolver(z3Interface->z3Interface());
|
||||
}
|
||||
#endif
|
||||
if (!usesZ3)
|
||||
{
|
||||
auto smtlib2Interface = dynamic_cast<CHCSmtLib2Interface*>(m_interface.get());
|
||||
smtlib2Interface->reset();
|
||||
solAssert(smtlib2Interface, "");
|
||||
m_context.setSolver(smtlib2Interface->smtlib2Interface());
|
||||
}
|
||||
|
||||
m_context.clear();
|
||||
m_context.setAssertionAccumulation(false);
|
||||
}
|
||||
|
||||
void CHC::resetContractAnalysis()
|
||||
{
|
||||
m_stateSorts.clear();
|
||||
m_stateVariables.clear();
|
||||
m_unknownFunctionCallSeen = false;
|
||||
m_breakDest = nullptr;
|
||||
@ -737,117 +752,18 @@ set<frontend::Expression const*, CHC::IdCompare> CHC::transactionAssertions(ASTN
|
||||
return assertions;
|
||||
}
|
||||
|
||||
vector<smtutil::SortPointer> CHC::stateSorts(ContractDefinition const& _contract)
|
||||
SortPointer CHC::sort(FunctionDefinition const& _function)
|
||||
{
|
||||
return applyMap(
|
||||
SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract),
|
||||
[](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); }
|
||||
);
|
||||
return functionSort(_function, m_currentContract);
|
||||
}
|
||||
|
||||
smtutil::SortPointer CHC::constructorSort()
|
||||
{
|
||||
solAssert(m_currentContract, "");
|
||||
if (auto const* constructor = m_currentContract->constructor())
|
||||
return sort(*constructor);
|
||||
|
||||
return make_shared<smtutil::FunctionSort>(
|
||||
vector<smtutil::SortPointer>{smtutil::SortProvider::uintSort} + m_stateSorts,
|
||||
smtutil::SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
smtutil::SortPointer CHC::interfaceSort()
|
||||
{
|
||||
solAssert(m_currentContract, "");
|
||||
return interfaceSort(*m_currentContract);
|
||||
}
|
||||
|
||||
smtutil::SortPointer CHC::nondetInterfaceSort()
|
||||
{
|
||||
solAssert(m_currentContract, "");
|
||||
return nondetInterfaceSort(*m_currentContract);
|
||||
}
|
||||
|
||||
smtutil::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
|
||||
{
|
||||
return make_shared<smtutil::FunctionSort>(
|
||||
stateSorts(_contract),
|
||||
smtutil::SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
smtutil::SortPointer CHC::nondetInterfaceSort(ContractDefinition const& _contract)
|
||||
{
|
||||
auto sorts = stateSorts(_contract);
|
||||
return make_shared<smtutil::FunctionSort>(
|
||||
sorts + sorts,
|
||||
smtutil::SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
smtutil::SortPointer CHC::arity0FunctionSort() const
|
||||
{
|
||||
return make_shared<smtutil::FunctionSort>(
|
||||
vector<smtutil::SortPointer>(),
|
||||
smtutil::SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
/// A function in the symbolic CFG requires:
|
||||
/// - Index of failed assertion. 0 means no assertion failed.
|
||||
/// - 2 sets of state variables:
|
||||
/// - State variables at the beginning of the current function, immutable
|
||||
/// - Current state variables
|
||||
/// At the beginning of the function these must equal set 1
|
||||
/// - 2 sets of input variables:
|
||||
/// - Input variables at the beginning of the current function, immutable
|
||||
/// - Current input variables
|
||||
/// At the beginning of the function these must equal set 1
|
||||
/// - 1 set of output variables
|
||||
smtutil::SortPointer CHC::sort(FunctionDefinition const& _function)
|
||||
{
|
||||
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
|
||||
auto inputSorts = applyMap(_function.parameters(), smtSort);
|
||||
auto outputSorts = applyMap(_function.returnParameters(), smtSort);
|
||||
return make_shared<smtutil::FunctionSort>(
|
||||
vector<smtutil::SortPointer>{smtutil::SortProvider::uintSort} + m_stateSorts + inputSorts + m_stateSorts + inputSorts + outputSorts,
|
||||
smtutil::SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
smtutil::SortPointer CHC::sort(ASTNode const* _node)
|
||||
SortPointer CHC::sort(ASTNode const* _node)
|
||||
{
|
||||
if (auto funDef = dynamic_cast<FunctionDefinition const*>(_node))
|
||||
return sort(*funDef);
|
||||
|
||||
auto fSort = dynamic_pointer_cast<smtutil::FunctionSort>(sort(*m_currentFunction));
|
||||
solAssert(fSort, "");
|
||||
|
||||
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
|
||||
return make_shared<smtutil::FunctionSort>(
|
||||
fSort->domain + applyMap(m_currentFunction->localVariables(), smtSort),
|
||||
smtutil::SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
smtutil::SortPointer CHC::summarySort(FunctionDefinition const& _function, ContractDefinition const& _contract)
|
||||
{
|
||||
auto stateVariables = SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract);
|
||||
auto sorts = stateSorts(_contract);
|
||||
|
||||
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
|
||||
auto inputSorts = applyMap(_function.parameters(), smtSort);
|
||||
auto outputSorts = applyMap(_function.returnParameters(), smtSort);
|
||||
return make_shared<smtutil::FunctionSort>(
|
||||
vector<smtutil::SortPointer>{smtutil::SortProvider::uintSort} +
|
||||
sorts +
|
||||
inputSorts +
|
||||
sorts +
|
||||
inputSorts +
|
||||
outputSorts,
|
||||
smtutil::SortProvider::boolSort
|
||||
);
|
||||
solAssert(m_currentFunction, "");
|
||||
return functionBodySort(*m_currentFunction, m_currentContract);
|
||||
}
|
||||
|
||||
Predicate const* CHC::createSymbolicBlock(SortPointer _sort, string const& _name, ASTNode const* _node)
|
||||
@ -861,27 +777,23 @@ void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
|
||||
{
|
||||
for (auto const& node: _source.nodes())
|
||||
if (auto const* contract = dynamic_cast<ContractDefinition const*>(node.get()))
|
||||
{
|
||||
string suffix = contract->name() + "_" + to_string(contract->id());
|
||||
m_interfaces[contract] = createSymbolicBlock(interfaceSort(*contract), "interface_" + suffix);
|
||||
m_nondetInterfaces[contract] = createSymbolicBlock(nondetInterfaceSort(*contract), "nondet_interface_" + suffix);
|
||||
|
||||
for (auto const* var: stateVariablesIncludingInheritedAndPrivate(*contract))
|
||||
if (!m_context.knownVariable(*var))
|
||||
createVariable(*var);
|
||||
|
||||
/// Base nondeterministic interface that allows
|
||||
/// 0 steps to be taken, used as base for the inductive
|
||||
/// rule for each function.
|
||||
auto const& iface = *m_nondetInterfaces.at(contract);
|
||||
auto state0 = stateVariablesAtIndex(0, *contract);
|
||||
addRule(iface(state0 + state0), "base_nondet");
|
||||
|
||||
for (auto const* base: contract->annotation().linearizedBaseContracts)
|
||||
{
|
||||
for (auto const* var: SMTEncoder::stateVariablesIncludingInheritedAndPrivate(*base))
|
||||
if (!m_context.knownVariable(*var))
|
||||
createVariable(*var);
|
||||
|
||||
if (!m_interfaces.count(base))
|
||||
{
|
||||
solAssert(!m_nondetInterfaces.count(base), "");
|
||||
string suffix = base->name() + "_" + to_string(base->id());
|
||||
m_interfaces.emplace(base, createSymbolicBlock(interfaceSort(*base), "interface_" + suffix, base));
|
||||
m_nondetInterfaces.emplace(base, createSymbolicBlock(nondetInterfaceSort(*base), "nondet_interface_" + suffix, base));
|
||||
|
||||
/// Base nondeterministic interface that allows
|
||||
/// 0 steps to be taken, used as base for the inductive
|
||||
/// rule for each function.
|
||||
auto const* iface = m_nondetInterfaces.at(base);
|
||||
auto state0 = stateVariablesAtIndex(0, *base);
|
||||
addRule((*iface)(state0 + state0), "base_nondet");
|
||||
}
|
||||
|
||||
for (auto const* function: base->definedFunctions())
|
||||
{
|
||||
for (auto var: function->parameters())
|
||||
@ -900,13 +812,11 @@ void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
|
||||
!base->isInterface()
|
||||
)
|
||||
{
|
||||
auto state1 = stateVariablesAtIndex(1, *base);
|
||||
auto state2 = stateVariablesAtIndex(2, *base);
|
||||
auto state1 = stateVariablesAtIndex(1, *contract);
|
||||
auto state2 = stateVariablesAtIndex(2, *contract);
|
||||
|
||||
auto const* iface = m_nondetInterfaces.at(base);
|
||||
auto state0 = stateVariablesAtIndex(0, *base);
|
||||
auto nondetPre = (*iface)(state0 + state1);
|
||||
auto nondetPost = (*iface)(state0 + state2);
|
||||
auto nondetPre = iface(state0 + state1);
|
||||
auto nondetPost = iface(state0 + state2);
|
||||
|
||||
vector<smtutil::Expression> args{m_error.currentValue()};
|
||||
args += state1 +
|
||||
@ -915,10 +825,10 @@ void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
|
||||
applyMap(function->parameters(), [this](auto _var) { return valueAtIndex(*_var, 1); }) +
|
||||
applyMap(function->returnParameters(), [this](auto _var) { return valueAtIndex(*_var, 1); });
|
||||
|
||||
connectBlocks(nondetPre, nondetPost, (*m_summaries.at(base).at(function))(args));
|
||||
connectBlocks(nondetPre, nondetPost, (*m_summaries.at(contract).at(function))(args));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
smtutil::Expression CHC::interface()
|
||||
@ -991,7 +901,7 @@ Predicate const* CHC::createBlock(ASTNode const* _node, string const& _prefix)
|
||||
Predicate const* CHC::createSummaryBlock(FunctionDefinition const& _function, ContractDefinition const& _contract)
|
||||
{
|
||||
auto block = createSymbolicBlock(
|
||||
summarySort(_function, _contract),
|
||||
functionSort(_function, &_contract),
|
||||
"summary_" + uniquePrefix() + "_" + predicateName(&_function, &_contract),
|
||||
&_function
|
||||
);
|
||||
@ -1161,14 +1071,14 @@ void CHC::addRule(smtutil::Expression const& _rule, string const& _ruleName)
|
||||
m_interface->addRule(_rule, _ruleName);
|
||||
}
|
||||
|
||||
pair<smtutil::CheckResult, CHCSolverInterface::CexGraph> CHC::query(smtutil::Expression const& _query, langutil::SourceLocation const& _location)
|
||||
pair<CheckResult, CHCSolverInterface::CexGraph> CHC::query(smtutil::Expression const& _query, langutil::SourceLocation const& _location)
|
||||
{
|
||||
smtutil::CheckResult result;
|
||||
CheckResult result;
|
||||
CHCSolverInterface::CexGraph cex;
|
||||
tie(result, cex) = m_interface->query(_query);
|
||||
switch (result)
|
||||
{
|
||||
case smtutil::CheckResult::SATISFIABLE:
|
||||
case CheckResult::SATISFIABLE:
|
||||
{
|
||||
#ifdef HAVE_Z3
|
||||
// Even though the problem is SAT, Spacer's pre processing makes counterexamples incomplete.
|
||||
@ -1177,26 +1087,26 @@ pair<smtutil::CheckResult, CHCSolverInterface::CexGraph> CHC::query(smtutil::Exp
|
||||
solAssert(spacer, "");
|
||||
spacer->setSpacerOptions(false);
|
||||
|
||||
smtutil::CheckResult resultNoOpt;
|
||||
CheckResult resultNoOpt;
|
||||
CHCSolverInterface::CexGraph cexNoOpt;
|
||||
tie(resultNoOpt, cexNoOpt) = m_interface->query(_query);
|
||||
|
||||
if (resultNoOpt == smtutil::CheckResult::SATISFIABLE)
|
||||
if (resultNoOpt == CheckResult::SATISFIABLE)
|
||||
cex = move(cexNoOpt);
|
||||
|
||||
spacer->setSpacerOptions(true);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case smtutil::CheckResult::UNSATISFIABLE:
|
||||
case CheckResult::UNSATISFIABLE:
|
||||
break;
|
||||
case smtutil::CheckResult::UNKNOWN:
|
||||
case CheckResult::UNKNOWN:
|
||||
break;
|
||||
case smtutil::CheckResult::CONFLICTING:
|
||||
m_outerErrorReporter.warning(1988_error, _location, "At least two SMT solvers provided conflicting answers. Results might not be sound.");
|
||||
case CheckResult::CONFLICTING:
|
||||
m_outerErrorReporter.warning(1988_error, _location, "CHC: At least two SMT solvers provided conflicting answers. Results might not be sound.");
|
||||
break;
|
||||
case smtutil::CheckResult::ERROR:
|
||||
m_outerErrorReporter.warning(1218_error, _location, "Error trying to invoke SMT solver.");
|
||||
case CheckResult::ERROR:
|
||||
m_outerErrorReporter.warning(1218_error, _location, "CHC: Error trying to invoke SMT solver.");
|
||||
break;
|
||||
}
|
||||
return {result, cex};
|
||||
@ -1210,6 +1120,16 @@ void CHC::addVerificationTarget(
|
||||
smtutil::Expression _errorId
|
||||
)
|
||||
{
|
||||
solAssert(m_currentContract || m_currentFunction, "");
|
||||
SourceUnit const* source = nullptr;
|
||||
if (m_currentContract)
|
||||
source = sourceUnitContaining(*m_currentContract);
|
||||
else
|
||||
source = sourceUnitContaining(*m_currentFunction);
|
||||
solAssert(source, "");
|
||||
if (!source->annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker))
|
||||
return;
|
||||
|
||||
m_verificationTargets.emplace(_scope, CHCVerificationTarget{{_type, _from, _constraints}, _errorId});
|
||||
}
|
||||
|
||||
@ -1251,7 +1171,7 @@ void CHC::checkVerificationTargets()
|
||||
if (target.type == VerificationTarget::Type::PopEmptyArray)
|
||||
{
|
||||
solAssert(dynamic_cast<FunctionCall const*>(scope), "");
|
||||
satMsg = "Empty array \"pop\" detected here";
|
||||
satMsg = "Empty array \"pop\" detected here.";
|
||||
unknownMsg = "Empty array \"pop\" might happen here.";
|
||||
errorReporterId = 2529_error;
|
||||
}
|
||||
@ -1267,8 +1187,8 @@ void CHC::checkVerificationTargets()
|
||||
if (!intType)
|
||||
intType = TypeProvider::uint256();
|
||||
|
||||
satMsgUnderflow = "Underflow (resulting value less than " + formatNumberReadable(intType->minValue()) + ") happens here";
|
||||
satMsgOverflow = "Overflow (resulting value larger than " + formatNumberReadable(intType->maxValue()) + ") happens here";
|
||||
satMsgUnderflow = "Underflow (resulting value less than " + formatNumberReadable(intType->minValue()) + ") happens here.";
|
||||
satMsgOverflow = "Overflow (resulting value larger than " + formatNumberReadable(intType->maxValue()) + ") happens here.";
|
||||
if (target.type == VerificationTarget::Type::Underflow)
|
||||
{
|
||||
satMsg = satMsgUnderflow;
|
||||
@ -1314,7 +1234,7 @@ void CHC::checkAssertTarget(ASTNode const* _scope, CHCVerificationTarget const&
|
||||
solAssert(it != m_errorIds.end(), "");
|
||||
unsigned errorId = it->second;
|
||||
|
||||
checkAndReportTarget(assertion, _target, errorId, 6328_error, "Assertion violation happens here");
|
||||
checkAndReportTarget(assertion, _target, errorId, 6328_error, "Assertion violation happens here.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1333,9 +1253,9 @@ void CHC::checkAndReportTarget(
|
||||
createErrorBlock();
|
||||
connectBlocks(_target.value, error(), _target.constraints && (_target.errorId == _errorId));
|
||||
auto const& [result, model] = query(error(), _scope->location());
|
||||
if (result == smtutil::CheckResult::UNSATISFIABLE)
|
||||
if (result == CheckResult::UNSATISFIABLE)
|
||||
m_safeTargets[_scope].insert(_target.type);
|
||||
else if (result == smtutil::CheckResult::SATISFIABLE)
|
||||
else if (result == CheckResult::SATISFIABLE)
|
||||
{
|
||||
solAssert(!_satMsg.empty(), "");
|
||||
m_unsafeTargets[_scope].insert(_target.type);
|
||||
@ -1344,21 +1264,21 @@ void CHC::checkAndReportTarget(
|
||||
m_outerErrorReporter.warning(
|
||||
_errorReporterId,
|
||||
_scope->location(),
|
||||
_satMsg,
|
||||
SecondarySourceLocation().append(" for:\n" + *cex, SourceLocation{})
|
||||
"CHC: " + _satMsg,
|
||||
SecondarySourceLocation().append("\nCounterexample:\n" + *cex, SourceLocation{})
|
||||
);
|
||||
else
|
||||
m_outerErrorReporter.warning(
|
||||
_errorReporterId,
|
||||
_scope->location(),
|
||||
_satMsg + "."
|
||||
"CHC: " + _satMsg
|
||||
);
|
||||
}
|
||||
else if (!_unknownMsg.empty())
|
||||
m_outerErrorReporter.warning(
|
||||
_errorReporterId,
|
||||
_scope->location(),
|
||||
_unknownMsg
|
||||
"CHC: " + _unknownMsg
|
||||
);
|
||||
}
|
||||
|
||||
@ -1459,7 +1379,11 @@ optional<string> CHC::generateCounterexample(CHCSolverInterface::CexGraph const&
|
||||
/// Recurse on the next interface node which represents the previous transaction
|
||||
/// or stop.
|
||||
if (interfaceId)
|
||||
{
|
||||
Predicate const* interfacePredicate = Predicate::predicate(_graph.nodes.at(*interfaceId).first);
|
||||
solAssert(interfacePredicate && interfacePredicate->isInterface(), "");
|
||||
node = *interfaceId;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
@ -1467,7 +1391,7 @@ optional<string> CHC::generateCounterexample(CHCSolverInterface::CexGraph const&
|
||||
return localState + "\nTransaction trace:\n" + boost::algorithm::join(boost::adaptors::reverse(path), "\n");
|
||||
}
|
||||
|
||||
string CHC::cex2dot(smtutil::CHCSolverInterface::CexGraph const& _cex)
|
||||
string CHC::cex2dot(CHCSolverInterface::CexGraph const& _cex)
|
||||
{
|
||||
string dot = "digraph {\n";
|
||||
|
||||
@ -1488,6 +1412,11 @@ string CHC::uniquePrefix()
|
||||
return to_string(m_blockCounter++);
|
||||
}
|
||||
|
||||
string CHC::contractSuffix(ContractDefinition const& _contract)
|
||||
{
|
||||
return _contract.name() + "_" + to_string(_contract.id());
|
||||
}
|
||||
|
||||
unsigned CHC::newErrorId(frontend::Expression const& _expr)
|
||||
{
|
||||
unsigned errorId = m_context.newUniqueId();
|
||||
|
@ -117,19 +117,8 @@ private:
|
||||
|
||||
/// Sort helpers.
|
||||
//@{
|
||||
static std::vector<smtutil::SortPointer> stateSorts(ContractDefinition const& _contract);
|
||||
smtutil::SortPointer constructorSort();
|
||||
smtutil::SortPointer interfaceSort();
|
||||
smtutil::SortPointer nondetInterfaceSort();
|
||||
static smtutil::SortPointer interfaceSort(ContractDefinition const& _const);
|
||||
static smtutil::SortPointer nondetInterfaceSort(ContractDefinition const& _const);
|
||||
smtutil::SortPointer arity0FunctionSort() const;
|
||||
smtutil::SortPointer sort(FunctionDefinition const& _function);
|
||||
smtutil::SortPointer sort(ASTNode const* _block);
|
||||
/// @returns the sort of a predicate that represents the summary of _function in the scope of _contract.
|
||||
/// The _contract is also needed because the same function might be in many contracts due to inheritance,
|
||||
/// where the sort changes because the set of state variables might change.
|
||||
static smtutil::SortPointer summarySort(FunctionDefinition const& _function, ContractDefinition const& _contract);
|
||||
//@}
|
||||
|
||||
/// Predicate helpers.
|
||||
@ -141,8 +130,6 @@ private:
|
||||
/// in a given _source.
|
||||
void defineInterfacesAndSummaries(SourceUnit const& _source);
|
||||
|
||||
/// Genesis predicate.
|
||||
smtutil::Expression genesis() { return (*m_genesisPredicate)({}); }
|
||||
/// Interface predicate over current variables.
|
||||
smtutil::Expression interface();
|
||||
smtutil::Expression interface(ContractDefinition const& _contract);
|
||||
@ -248,10 +235,13 @@ private:
|
||||
|
||||
/// Misc.
|
||||
//@{
|
||||
/// Returns a prefix to be used in a new unique block name
|
||||
/// @returns a prefix to be used in a new unique block name
|
||||
/// and increases the block counter.
|
||||
std::string uniquePrefix();
|
||||
|
||||
/// @returns a suffix to be used by contract related predicates.
|
||||
std::string contractSuffix(ContractDefinition const& _contract);
|
||||
|
||||
/// @returns a new unique error id associated with _expr and stores
|
||||
/// it into m_errorIds.
|
||||
unsigned newErrorId(Expression const& _expr);
|
||||
@ -259,13 +249,6 @@ private:
|
||||
|
||||
/// Predicates.
|
||||
//@{
|
||||
/// Genesis predicate.
|
||||
Predicate const* m_genesisPredicate = nullptr;
|
||||
|
||||
/// Implicit constructor predicate.
|
||||
/// Explicit constructors are handled as functions.
|
||||
Predicate const* m_implicitConstructorPredicate = nullptr;
|
||||
|
||||
/// Constructor summary predicate, exists after the constructor
|
||||
/// (implicit or explicit) and before the interface.
|
||||
Predicate const* m_constructorSummaryPredicate = nullptr;
|
||||
@ -293,16 +276,10 @@ private:
|
||||
"error",
|
||||
m_context
|
||||
};
|
||||
|
||||
/// Maps predicate names to the ASTNodes they came from.
|
||||
std::map<std::string, ASTNode const*> m_symbolFunction;
|
||||
//@}
|
||||
|
||||
/// Variables.
|
||||
//@{
|
||||
/// State variables sorts.
|
||||
/// Used by all predicates.
|
||||
std::vector<smtutil::SortPointer> m_stateSorts;
|
||||
/// State variables.
|
||||
/// Used to create all predicates.
|
||||
std::vector<VariableDeclaration const*> m_stateVariables;
|
||||
|
111
libsolidity/formal/PredicateSort.cpp
Normal file
111
libsolidity/formal/PredicateSort.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#include <libsolidity/formal/PredicateSort.h>
|
||||
|
||||
#include <libsolidity/formal/SMTEncoder.h>
|
||||
#include <libsolidity/formal/SymbolicTypes.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity::util;
|
||||
using namespace solidity::smtutil;
|
||||
|
||||
namespace solidity::frontend::smt
|
||||
{
|
||||
|
||||
SortPointer interfaceSort(ContractDefinition const& _contract)
|
||||
{
|
||||
return make_shared<FunctionSort>(
|
||||
stateSorts(_contract),
|
||||
SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
SortPointer nondetInterfaceSort(ContractDefinition const& _contract)
|
||||
{
|
||||
auto varSorts = stateSorts(_contract);
|
||||
return make_shared<FunctionSort>(
|
||||
varSorts + varSorts,
|
||||
SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
SortPointer implicitConstructorSort()
|
||||
{
|
||||
return arity0FunctionSort();
|
||||
}
|
||||
|
||||
SortPointer constructorSort(ContractDefinition const& _contract)
|
||||
{
|
||||
if (auto const* constructor = _contract.constructor())
|
||||
return functionSort(*constructor, &_contract);
|
||||
|
||||
return make_shared<FunctionSort>(
|
||||
vector<SortPointer>{SortProvider::uintSort} + stateSorts(_contract),
|
||||
SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
SortPointer functionSort(FunctionDefinition const& _function, ContractDefinition const* _contract)
|
||||
{
|
||||
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
|
||||
auto varSorts = _contract ? stateSorts(*_contract) : vector<SortPointer>{};
|
||||
auto inputSorts = applyMap(_function.parameters(), smtSort);
|
||||
auto outputSorts = applyMap(_function.returnParameters(), smtSort);
|
||||
return make_shared<FunctionSort>(
|
||||
vector<SortPointer>{SortProvider::uintSort} +
|
||||
varSorts +
|
||||
inputSorts +
|
||||
varSorts +
|
||||
inputSorts +
|
||||
outputSorts,
|
||||
SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
SortPointer functionBodySort(FunctionDefinition const& _function, ContractDefinition const* _contract)
|
||||
{
|
||||
auto fSort = dynamic_pointer_cast<FunctionSort>(functionSort(_function, _contract));
|
||||
solAssert(fSort, "");
|
||||
|
||||
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
|
||||
return make_shared<FunctionSort>(
|
||||
fSort->domain + applyMap(_function.localVariables(), smtSort),
|
||||
SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
SortPointer arity0FunctionSort()
|
||||
{
|
||||
return make_shared<FunctionSort>(
|
||||
vector<SortPointer>(),
|
||||
SortProvider::boolSort
|
||||
);
|
||||
}
|
||||
|
||||
/// Helpers
|
||||
|
||||
vector<SortPointer> stateSorts(ContractDefinition const& _contract)
|
||||
{
|
||||
return applyMap(
|
||||
SMTEncoder::stateVariablesIncludingInheritedAndPrivate(_contract),
|
||||
[](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); }
|
||||
);
|
||||
}
|
||||
|
||||
}
|
82
libsolidity/formal/PredicateSort.h
Normal file
82
libsolidity/formal/PredicateSort.h
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/formal/Predicate.h>
|
||||
|
||||
#include <libsmtutil/Sorts.h>
|
||||
|
||||
namespace solidity::frontend::smt
|
||||
{
|
||||
|
||||
/**
|
||||
* This file represents the specification for CHC predicate sorts.
|
||||
* Types of predicates:
|
||||
*
|
||||
* 1. Interface
|
||||
* The idle state of a contract. Signature:
|
||||
* interface(stateVariables).
|
||||
*
|
||||
* 2. Nondet interface
|
||||
* The nondeterminism behavior of a contract. Signature:
|
||||
* nondet_interface(stateVariables, stateVariables').
|
||||
*
|
||||
* 3. Implicit constructor
|
||||
* The implicit constructor of a contract, that is, without input parameters. Signature:
|
||||
* implicit_constructor().
|
||||
*
|
||||
* 4. Constructor entry/summary
|
||||
* The summary of an implicit constructor. Signature:
|
||||
* constructor_summary(error, stateVariables').
|
||||
*
|
||||
* 5. Function entry/summary
|
||||
* The entry point of a function definition. Signature:
|
||||
* function_entry(error, stateVariables, inputVariables, stateVariables', inputVariables', outputVariables').
|
||||
*
|
||||
* 6. Function body
|
||||
* Use for any predicate within a function. Signature:
|
||||
* function_body(error, stateVariables, inputVariables, stateVariables', inputVariables', outputVariables', localVariables).
|
||||
*/
|
||||
|
||||
/// @returns the interface predicate sort for _contract.
|
||||
smtutil::SortPointer interfaceSort(ContractDefinition const& _contract);
|
||||
|
||||
/// @returns the nondeterminisc interface predicate sort for _contract.
|
||||
smtutil::SortPointer nondetInterfaceSort(ContractDefinition const& _contract);
|
||||
|
||||
/// @returns the implicit constructor predicate sort.
|
||||
smtutil::SortPointer implicitConstructorSort();
|
||||
|
||||
/// @returns the constructor entry/summary predicate sort for _contract.
|
||||
smtutil::SortPointer constructorSort(ContractDefinition const& _contract);
|
||||
|
||||
/// @returns the function entry/summary predicate sort for _function contained in _contract.
|
||||
smtutil::SortPointer functionSort(FunctionDefinition const& _function, ContractDefinition const* _contract);
|
||||
|
||||
/// @returns the function body predicate sort for _function contained in _contract.
|
||||
smtutil::SortPointer functionBodySort(FunctionDefinition const& _function, ContractDefinition const* _contract);
|
||||
|
||||
/// @returns the sort of a predicate without parameters.
|
||||
smtutil::SortPointer arity0FunctionSort();
|
||||
|
||||
/// Helpers
|
||||
|
||||
std::vector<smtutil::SortPointer> stateSorts(ContractDefinition const& _contract) ;
|
||||
|
||||
}
|
@ -23,6 +23,7 @@
|
||||
#include <libsolidity/formal/SymbolicTypes.h>
|
||||
|
||||
#include <libsmtutil/SMTPortfolio.h>
|
||||
#include <libsmtutil/Helpers.h>
|
||||
|
||||
#include <boost/range/adaptors.hpp>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
@ -133,10 +134,7 @@ bool SMTEncoder::visit(FunctionDefinition const& _function)
|
||||
if (_function.isConstructor())
|
||||
inlineConstructorHierarchy(dynamic_cast<ContractDefinition const&>(*_function.scope()));
|
||||
|
||||
// Base constructors' parameters should be set by explicit calls,
|
||||
// but the most derived one needs to be initialized.
|
||||
if (_function.scope() == m_currentContract)
|
||||
initializeLocalVariables(_function);
|
||||
initializeLocalVariables(_function);
|
||||
|
||||
_function.parameterList().accept(*this);
|
||||
if (_function.returnParameterList())
|
||||
@ -354,31 +352,10 @@ void SMTEncoder::endVisit(Assignment const& _assignment)
|
||||
{
|
||||
createExpr(_assignment);
|
||||
|
||||
static set<Token> const compoundOps{
|
||||
Token::AssignAdd,
|
||||
Token::AssignSub,
|
||||
Token::AssignMul,
|
||||
Token::AssignDiv,
|
||||
Token::AssignMod
|
||||
};
|
||||
Token op = _assignment.assignmentOperator();
|
||||
if (op != Token::Assign && !compoundOps.count(op))
|
||||
{
|
||||
Expression const* identifier = &_assignment.leftHandSide();
|
||||
if (auto const* indexAccess = dynamic_cast<IndexAccess const*>(identifier))
|
||||
identifier = leftmostBase(*indexAccess);
|
||||
// Give it a new index anyway to keep the SSA scheme sound.
|
||||
solAssert(identifier, "");
|
||||
if (auto varDecl = identifierToVariable(*identifier))
|
||||
m_context.newValue(*varDecl);
|
||||
solAssert(TokenTraits::isAssignmentOp(op), "");
|
||||
|
||||
m_errorReporter.warning(
|
||||
9149_error,
|
||||
_assignment.location(),
|
||||
"Assertion checker does not yet implement this assignment operator."
|
||||
);
|
||||
}
|
||||
else if (!smt::isSupportedType(_assignment.annotation().type->category()))
|
||||
if (!smt::isSupportedType(*_assignment.annotation().type))
|
||||
{
|
||||
// Give it a new index anyway to keep the SSA scheme sound.
|
||||
|
||||
@ -396,15 +373,14 @@ void SMTEncoder::endVisit(Assignment const& _assignment)
|
||||
else
|
||||
{
|
||||
auto const& type = _assignment.annotation().type;
|
||||
auto rightHandSide = compoundOps.count(op) ?
|
||||
compoundAssignment(_assignment) :
|
||||
expr(_assignment.rightHandSide(), type);
|
||||
auto rightHandSide = op == Token::Assign ?
|
||||
expr(_assignment.rightHandSide(), type) :
|
||||
compoundAssignment(_assignment);
|
||||
defineExpr(_assignment, rightHandSide);
|
||||
assignment(
|
||||
_assignment.leftHandSide(),
|
||||
expr(_assignment, type),
|
||||
type,
|
||||
_assignment.location()
|
||||
type
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -463,20 +439,19 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
|
||||
createExpr(_op);
|
||||
|
||||
auto const* subExpr = innermostTuple(_op.subExpression());
|
||||
|
||||
auto type = _op.annotation().type;
|
||||
switch (_op.getOperator())
|
||||
{
|
||||
case Token::Not: // !
|
||||
{
|
||||
solAssert(smt::isBool(_op.annotation().type->category()), "");
|
||||
solAssert(smt::isBool(*type), "");
|
||||
defineExpr(_op, !expr(*subExpr));
|
||||
break;
|
||||
}
|
||||
case Token::Inc: // ++ (pre- or postfix)
|
||||
case Token::Dec: // -- (pre- or postfix)
|
||||
{
|
||||
auto cat = _op.annotation().type->category();
|
||||
solAssert(smt::isInteger(cat) || smt::isFixedPoint(cat), "");
|
||||
solAssert(smt::isInteger(*type) || smt::isFixedPoint(*type), "");
|
||||
solAssert(subExpr->annotation().willBeWrittenTo, "");
|
||||
if (auto identifier = dynamic_cast<Identifier const*>(subExpr))
|
||||
{
|
||||
@ -487,12 +462,15 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
|
||||
defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue);
|
||||
assignment(*decl, newValue);
|
||||
}
|
||||
else if (dynamic_cast<IndexAccess const*>(subExpr))
|
||||
else if (
|
||||
dynamic_cast<IndexAccess const*>(&_op.subExpression()) ||
|
||||
dynamic_cast<MemberAccess const*>(&_op.subExpression())
|
||||
)
|
||||
{
|
||||
auto innerValue = expr(*subExpr);
|
||||
auto newValue = _op.getOperator() == Token::Inc ? innerValue + 1 : innerValue - 1;
|
||||
defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue);
|
||||
arrayIndexAssignment(*subExpr, newValue);
|
||||
indexOrMemberAssignment(_op.subExpression(), newValue);
|
||||
}
|
||||
else
|
||||
m_errorReporter.warning(
|
||||
@ -521,14 +499,13 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
|
||||
auto const& symbVar = m_context.expression(*subExpr);
|
||||
symbVar->increaseIndex();
|
||||
m_context.setZeroValue(*symbVar);
|
||||
if (dynamic_cast<IndexAccess const*>(subExpr))
|
||||
arrayIndexAssignment(*subExpr, symbVar->currentValue());
|
||||
if (
|
||||
dynamic_cast<IndexAccess const*>(&_op.subExpression()) ||
|
||||
dynamic_cast<MemberAccess const*>(&_op.subExpression())
|
||||
)
|
||||
indexOrMemberAssignment(_op.subExpression(), symbVar->currentValue());
|
||||
else
|
||||
m_errorReporter.warning(
|
||||
2683_error,
|
||||
_op.location(),
|
||||
"Assertion checker does not yet implement \"delete\" for this expression."
|
||||
);
|
||||
solAssert(false, "");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -571,7 +548,7 @@ void SMTEncoder::endVisit(BinaryOperation const& _op)
|
||||
arithmeticOperation(_op);
|
||||
else if (TokenTraits::isCompareOp(_op.getOperator()))
|
||||
compareOperation(_op);
|
||||
else if (TokenTraits::isBitOp(_op.getOperator()))
|
||||
else if (TokenTraits::isBitOp(_op.getOperator()) || TokenTraits::isShiftOp(_op.getOperator()))
|
||||
bitwiseOperation(_op);
|
||||
else
|
||||
m_errorReporter.warning(
|
||||
@ -634,6 +611,10 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall)
|
||||
case FunctionType::Kind::Require:
|
||||
visitRequire(_funCall);
|
||||
break;
|
||||
case FunctionType::Kind::Revert:
|
||||
// Revert is a special case of require and equals to `require(false)`
|
||||
addPathImpliedExpression(smtutil::Expression(false));
|
||||
break;
|
||||
case FunctionType::Kind::GasLeft:
|
||||
visitGasLeft(_funCall);
|
||||
break;
|
||||
@ -674,6 +655,17 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall)
|
||||
case FunctionType::Kind::ArrayPop:
|
||||
arrayPop(_funCall);
|
||||
break;
|
||||
case FunctionType::Kind::Log0:
|
||||
case FunctionType::Kind::Log1:
|
||||
case FunctionType::Kind::Log2:
|
||||
case FunctionType::Kind::Log3:
|
||||
case FunctionType::Kind::Log4:
|
||||
case FunctionType::Kind::Event:
|
||||
// These can be safely ignored.
|
||||
break;
|
||||
case FunctionType::Kind::ObjectCreation:
|
||||
visitObjectCreation(_funCall);
|
||||
return;
|
||||
default:
|
||||
m_errorReporter.warning(
|
||||
4588_error,
|
||||
@ -746,6 +738,25 @@ void SMTEncoder::visitGasLeft(FunctionCall const& _funCall)
|
||||
m_context.addAssertion(symbolicVar->currentValue() <= symbolicVar->valueAtIndex(index - 1));
|
||||
}
|
||||
|
||||
void SMTEncoder::visitObjectCreation(FunctionCall const& _funCall)
|
||||
{
|
||||
auto const& args = _funCall.arguments();
|
||||
solAssert(args.size() >= 1, "");
|
||||
auto argType = args.front()->annotation().type->category();
|
||||
solAssert(argType == Type::Category::Integer || argType == Type::Category::RationalNumber, "");
|
||||
|
||||
smtutil::Expression arraySize = expr(*args.front());
|
||||
setSymbolicUnknownValue(arraySize, TypeProvider::uint256(), m_context);
|
||||
|
||||
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(_funCall));
|
||||
solAssert(symbArray, "");
|
||||
smt::setSymbolicZeroValue(*symbArray, m_context);
|
||||
auto zeroElements = symbArray->elements();
|
||||
symbArray->increaseIndex();
|
||||
m_context.addAssertion(symbArray->length() == arraySize);
|
||||
m_context.addAssertion(symbArray->elements() == zeroElements);
|
||||
}
|
||||
|
||||
void SMTEncoder::endVisit(Identifier const& _identifier)
|
||||
{
|
||||
if (auto decl = identifierToVariable(_identifier))
|
||||
@ -759,6 +770,13 @@ void SMTEncoder::endVisit(Identifier const& _identifier)
|
||||
defineExpr(_identifier, m_context.state().thisAddress());
|
||||
m_uninterpretedTerms.insert(&_identifier);
|
||||
}
|
||||
// Ignore the builtin abi, it is handled in FunctionCall.
|
||||
// TODO: ignore MagicType in general (abi, block, msg, tx, type)
|
||||
else if (auto magicType = dynamic_cast<MagicType const*>(_identifier.annotation().type); magicType && magicType->kind() == MagicType::Kind::ABI)
|
||||
{
|
||||
solAssert(_identifier.name() == "abi", "");
|
||||
return;
|
||||
}
|
||||
else
|
||||
createExpr(_identifier);
|
||||
}
|
||||
@ -782,12 +800,19 @@ void SMTEncoder::visitTypeConversion(FunctionCall const& _funCall)
|
||||
auto argument = _funCall.arguments().front();
|
||||
unsigned argSize = argument->annotation().type->storageBytes();
|
||||
unsigned castSize = _funCall.annotation().type->storageBytes();
|
||||
if (argSize == castSize)
|
||||
auto const& funCallCategory = _funCall.annotation().type->category();
|
||||
// Allow casting number literals to address.
|
||||
// TODO: remove the isNegative() check once the type checker disallows this
|
||||
if (
|
||||
auto const* numberType = dynamic_cast<RationalNumberType const*>(argument->annotation().type);
|
||||
numberType && !numberType->isNegative() && (funCallCategory == Type::Category::Address)
|
||||
)
|
||||
defineExpr(_funCall, numberType->literalValue(nullptr));
|
||||
else if (argSize == castSize)
|
||||
defineExpr(_funCall, expr(*argument));
|
||||
else
|
||||
{
|
||||
m_context.setUnknownValue(*m_context.expression(_funCall));
|
||||
auto const& funCallCategory = _funCall.annotation().type->category();
|
||||
// TODO: truncating and bytesX needs a different approach because of right padding.
|
||||
if (funCallCategory == Type::Category::Integer || funCallCategory == Type::Category::Address)
|
||||
{
|
||||
@ -826,12 +851,25 @@ void SMTEncoder::endVisit(Literal const& _literal)
|
||||
{
|
||||
solAssert(_literal.annotation().type, "Expected type for AST node");
|
||||
Type const& type = *_literal.annotation().type;
|
||||
if (smt::isNumber(type.category()))
|
||||
if (smt::isNumber(type))
|
||||
defineExpr(_literal, smtutil::Expression(type.literalValue(&_literal)));
|
||||
else if (smt::isBool(type.category()))
|
||||
else if (smt::isBool(type))
|
||||
defineExpr(_literal, smtutil::Expression(_literal.token() == Token::TrueLiteral ? true : false));
|
||||
else if (smt::isStringLiteral(type.category()))
|
||||
else if (smt::isStringLiteral(type))
|
||||
{
|
||||
createExpr(_literal);
|
||||
|
||||
// Add constraints for the length and values as it is known.
|
||||
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(_literal));
|
||||
solAssert(symbArray, "");
|
||||
|
||||
auto value = _literal.value();
|
||||
m_context.addAssertion(symbArray->length() == value.length());
|
||||
for (size_t i = 0; i < value.length(); i++)
|
||||
m_context.addAssertion(
|
||||
smtutil::Expression::select(symbArray->elements(), i) == size_t(value[i])
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_errorReporter.warning(
|
||||
@ -881,16 +919,43 @@ bool SMTEncoder::visit(MemberAccess const& _memberAccess)
|
||||
auto identifier = dynamic_cast<Identifier const*>(&_memberAccess.expression());
|
||||
if (exprType->category() == Type::Category::Magic)
|
||||
{
|
||||
string accessedName;
|
||||
if (identifier)
|
||||
accessedName = identifier->name();
|
||||
defineGlobalVariable(identifier->name() + "." + _memberAccess.memberName(), _memberAccess);
|
||||
else if (auto magicType = dynamic_cast<MagicType const*>(exprType); magicType->kind() == MagicType::Kind::MetaType)
|
||||
{
|
||||
auto const& memberName = _memberAccess.memberName();
|
||||
if (memberName == "min" || memberName == "max")
|
||||
{
|
||||
IntegerType const& integerType = dynamic_cast<IntegerType const&>(*magicType->typeArgument());
|
||||
defineExpr(_memberAccess, memberName == "min" ? integerType.minValue() : integerType.maxValue());
|
||||
}
|
||||
else if (memberName == "interfaceId")
|
||||
{
|
||||
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*magicType->typeArgument()).contractDefinition();
|
||||
defineExpr(_memberAccess, contract.interfaceId());
|
||||
}
|
||||
else
|
||||
// NOTE: supporting name, creationCode, runtimeCode would be easy enough, but the bytes/string they return are not
|
||||
// at all useable in the SMT checker currently
|
||||
m_errorReporter.warning(
|
||||
7507_error,
|
||||
_memberAccess.location(),
|
||||
"Assertion checker does not yet support this expression."
|
||||
);
|
||||
}
|
||||
else
|
||||
m_errorReporter.warning(
|
||||
9551_error,
|
||||
_memberAccess.location(),
|
||||
"Assertion checker does not yet support this expression."
|
||||
);
|
||||
defineGlobalVariable(accessedName + "." + _memberAccess.memberName(), _memberAccess);
|
||||
return false;
|
||||
}
|
||||
else if (smt::isNonRecursiveStruct(*exprType))
|
||||
{
|
||||
_memberAccess.expression().accept(*this);
|
||||
auto const& symbStruct = dynamic_pointer_cast<smt::SymbolicStructVariable>(m_context.expression(_memberAccess.expression()));
|
||||
defineExpr(_memberAccess, symbStruct->member(_memberAccess.memberName()));
|
||||
return false;
|
||||
}
|
||||
else if (exprType->category() == Type::Category::TypeType)
|
||||
@ -947,13 +1012,36 @@ void SMTEncoder::endVisit(IndexAccess const& _indexAccess)
|
||||
|
||||
if (_indexAccess.annotation().type->category() == Type::Category::TypeType)
|
||||
return;
|
||||
if (_indexAccess.baseExpression().annotation().type->category() == Type::Category::FixedBytes)
|
||||
if (auto const* type = dynamic_cast<FixedBytesType const*>(_indexAccess.baseExpression().annotation().type))
|
||||
{
|
||||
m_errorReporter.warning(
|
||||
7989_error,
|
||||
_indexAccess.location(),
|
||||
"Assertion checker does not yet support index accessing fixed bytes."
|
||||
);
|
||||
smtutil::Expression base = expr(_indexAccess.baseExpression());
|
||||
|
||||
if (type->numBytes() == 1)
|
||||
defineExpr(_indexAccess, base);
|
||||
else
|
||||
{
|
||||
auto [bvSize, isSigned] = smt::typeBvSizeAndSignedness(_indexAccess.baseExpression().annotation().type);
|
||||
solAssert(!isSigned, "");
|
||||
solAssert(bvSize >= 16, "");
|
||||
solAssert(bvSize % 8 == 0, "");
|
||||
|
||||
smtutil::Expression idx = expr(*_indexAccess.indexExpression());
|
||||
|
||||
auto bvBase = smtutil::Expression::int2bv(base, bvSize);
|
||||
auto bvShl = smtutil::Expression::int2bv(idx * 8, bvSize);
|
||||
auto bvShr = smtutil::Expression::int2bv(bvSize - 8, bvSize);
|
||||
auto result = (bvBase << bvShl) >> bvShr;
|
||||
|
||||
auto anyValue = expr(_indexAccess);
|
||||
m_context.expression(_indexAccess)->increaseIndex();
|
||||
unsigned numBytes = bvSize / 8;
|
||||
auto withBound = smtutil::Expression::ite(
|
||||
idx < numBytes,
|
||||
smtutil::Expression::bv2int(result, false),
|
||||
anyValue
|
||||
);
|
||||
defineExpr(_indexAccess, withBound);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -964,19 +1052,10 @@ void SMTEncoder::endVisit(IndexAccess const& _indexAccess)
|
||||
solAssert(varDecl, "");
|
||||
array = m_context.variable(*varDecl);
|
||||
}
|
||||
else if (auto const* innerAccess = dynamic_cast<IndexAccess const*>(&_indexAccess.baseExpression()))
|
||||
{
|
||||
solAssert(m_context.knownExpression(*innerAccess), "");
|
||||
array = m_context.expression(*innerAccess);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_errorReporter.warning(
|
||||
9118_error,
|
||||
_indexAccess.location(),
|
||||
"Assertion checker does not yet implement this expression."
|
||||
);
|
||||
return;
|
||||
solAssert(m_context.knownExpression(_indexAccess.baseExpression()), "");
|
||||
array = m_context.expression(_indexAccess.baseExpression());
|
||||
}
|
||||
|
||||
auto arrayVar = dynamic_pointer_cast<smt::SymbolicArrayVariable>(array);
|
||||
@ -1008,14 +1087,58 @@ void SMTEncoder::arrayAssignment()
|
||||
m_arrayAssignmentHappened = true;
|
||||
}
|
||||
|
||||
void SMTEncoder::arrayIndexAssignment(Expression const& _expr, smtutil::Expression const& _rightHandSide)
|
||||
void SMTEncoder::indexOrMemberAssignment(Expression const& _expr, smtutil::Expression const& _rightHandSide)
|
||||
{
|
||||
auto toStore = _rightHandSide;
|
||||
auto indexAccess = dynamic_cast<IndexAccess const*>(&_expr);
|
||||
solAssert(indexAccess, "");
|
||||
auto const* lastExpr = &_expr;
|
||||
while (true)
|
||||
{
|
||||
if (auto const& id = dynamic_cast<Identifier const*>(&indexAccess->baseExpression()))
|
||||
if (auto const* indexAccess = dynamic_cast<IndexAccess const*>(lastExpr))
|
||||
{
|
||||
auto const& base = indexAccess->baseExpression();
|
||||
if (dynamic_cast<Identifier const*>(&base))
|
||||
base.accept(*this);
|
||||
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(base));
|
||||
solAssert(symbArray, "");
|
||||
auto baseType = symbArray->type();
|
||||
toStore = smtutil::Expression::tuple_constructor(
|
||||
smtutil::Expression(make_shared<smtutil::SortSort>(smt::smtSort(*baseType)), baseType->toString(true)),
|
||||
{smtutil::Expression::store(symbArray->elements(), expr(*indexAccess->indexExpression()), toStore), symbArray->length()}
|
||||
);
|
||||
m_context.expression(*indexAccess)->increaseIndex();
|
||||
defineExpr(*indexAccess, smtutil::Expression::select(
|
||||
symbArray->elements(),
|
||||
expr(*indexAccess->indexExpression())
|
||||
));
|
||||
lastExpr = &indexAccess->baseExpression();
|
||||
}
|
||||
else if (auto const* memberAccess = dynamic_cast<MemberAccess const*>(lastExpr))
|
||||
{
|
||||
auto const& base = memberAccess->expression();
|
||||
if (dynamic_cast<Identifier const*>(&base))
|
||||
base.accept(*this);
|
||||
|
||||
if (
|
||||
auto const* structType = dynamic_cast<StructType const*>(base.annotation().type);
|
||||
structType && structType->recursive()
|
||||
)
|
||||
{
|
||||
m_errorReporter.warning(
|
||||
4375_error,
|
||||
memberAccess->location(),
|
||||
"Assertion checker does not support recursive structs."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
auto symbStruct = dynamic_pointer_cast<smt::SymbolicStructVariable>(m_context.expression(base));
|
||||
solAssert(symbStruct, "");
|
||||
symbStruct->assignMember(memberAccess->memberName(), toStore);
|
||||
toStore = symbStruct->currentValue();
|
||||
defineExpr(*memberAccess, symbStruct->member(memberAccess->memberName()));
|
||||
lastExpr = &memberAccess->expression();
|
||||
}
|
||||
else if (auto const& id = dynamic_cast<Identifier const*>(lastExpr))
|
||||
{
|
||||
auto varDecl = identifierToVariable(*id);
|
||||
solAssert(varDecl, "");
|
||||
@ -1023,43 +1146,22 @@ void SMTEncoder::arrayIndexAssignment(Expression const& _expr, smtutil::Expressi
|
||||
if (varDecl->hasReferenceOrMappingType())
|
||||
resetReferences(*varDecl);
|
||||
|
||||
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.variable(*varDecl));
|
||||
smtutil::Expression store = smtutil::Expression::store(
|
||||
symbArray->elements(),
|
||||
expr(*indexAccess->indexExpression()),
|
||||
toStore
|
||||
);
|
||||
auto oldLength = symbArray->length();
|
||||
symbArray->increaseIndex();
|
||||
m_context.addAssertion(symbArray->elements() == store);
|
||||
m_context.addAssertion(symbArray->length() == oldLength);
|
||||
// Update the SMT select value after the assignment,
|
||||
// necessary for sound models.
|
||||
defineExpr(*indexAccess, smtutil::Expression::select(
|
||||
symbArray->elements(),
|
||||
expr(*indexAccess->indexExpression())
|
||||
));
|
||||
|
||||
m_context.addAssertion(m_context.newValue(*varDecl) == toStore);
|
||||
m_context.expression(*id)->increaseIndex();
|
||||
defineExpr(*id,currentValue(*varDecl));
|
||||
break;
|
||||
}
|
||||
else if (auto base = dynamic_cast<IndexAccess const*>(&indexAccess->baseExpression()))
|
||||
{
|
||||
auto symbArray = dynamic_pointer_cast<smt::SymbolicArrayVariable>(m_context.expression(*base));
|
||||
solAssert(symbArray, "");
|
||||
auto baseType = base->annotation().type;
|
||||
toStore = smtutil::Expression::tuple_constructor(
|
||||
smtutil::Expression(make_shared<smtutil::SortSort>(smt::smtSort(*baseType)), baseType->toString(true)),
|
||||
{smtutil::Expression::store(symbArray->elements(), expr(*indexAccess->indexExpression()), toStore), symbArray->length()}
|
||||
);
|
||||
indexAccess = base;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_errorReporter.warning(
|
||||
9056_error,
|
||||
_expr.location(),
|
||||
"Assertion checker does not yet implement this expression."
|
||||
);
|
||||
auto type = lastExpr->annotation().type;
|
||||
if (
|
||||
dynamic_cast<ReferenceType const*>(type) ||
|
||||
dynamic_cast<MappingType const*>(type)
|
||||
)
|
||||
resetReferences(type);
|
||||
|
||||
m_context.expression(*lastExpr)->increaseIndex();
|
||||
m_context.addAssertion(expr(*lastExpr) == toStore);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1131,9 +1233,14 @@ void SMTEncoder::arrayPushPopAssign(Expression const& _expr, smtutil::Expression
|
||||
if (varDecl->hasReferenceOrMappingType())
|
||||
resetReferences(*varDecl);
|
||||
m_context.addAssertion(m_context.newValue(*varDecl) == _array);
|
||||
m_context.expression(*id)->increaseIndex();
|
||||
defineExpr(*id,currentValue(*varDecl));
|
||||
}
|
||||
else if (auto const* indexAccess = dynamic_cast<IndexAccess const*>(expr))
|
||||
arrayIndexAssignment(*indexAccess, _array);
|
||||
else if (
|
||||
dynamic_cast<IndexAccess const*>(expr) ||
|
||||
dynamic_cast<MemberAccess const*>(expr)
|
||||
)
|
||||
indexOrMemberAssignment(_expr, _array);
|
||||
else if (auto const* funCall = dynamic_cast<FunctionCall const*>(expr))
|
||||
{
|
||||
FunctionType const& funType = dynamic_cast<FunctionType const&>(*funCall->expression().annotation().type);
|
||||
@ -1156,12 +1263,6 @@ void SMTEncoder::arrayPushPopAssign(Expression const& _expr, smtutil::Expression
|
||||
arrayPushPopAssign(memberAccess->expression(), symbArray->currentValue());
|
||||
}
|
||||
}
|
||||
else if (dynamic_cast<MemberAccess const*>(expr))
|
||||
m_errorReporter.warning(
|
||||
9599_error,
|
||||
_expr.location(),
|
||||
"Assertion checker does not yet implement this expression."
|
||||
);
|
||||
else
|
||||
solAssert(false, "");
|
||||
}
|
||||
@ -1182,7 +1283,7 @@ void SMTEncoder::defineGlobalVariable(string const& _name, Expression const& _ex
|
||||
m_context.globalSymbol(_name)->increaseIndex();
|
||||
// The default behavior is not to increase the index since
|
||||
// most of the global values stay the same throughout a tx.
|
||||
if (smt::isSupportedType(_expr.annotation().type->category()))
|
||||
if (smt::isSupportedType(*_expr.annotation().type))
|
||||
defineExpr(_expr, m_context.globalSymbol(_name)->currentValue());
|
||||
}
|
||||
|
||||
@ -1331,17 +1432,70 @@ pair<smtutil::Expression, smtutil::Expression> SMTEncoder::arithmeticOperation(
|
||||
return {value, valueUnbounded};
|
||||
}
|
||||
|
||||
smtutil::Expression SMTEncoder::bitwiseOperation(
|
||||
Token _op,
|
||||
smtutil::Expression const& _left,
|
||||
smtutil::Expression const& _right,
|
||||
TypePointer const& _commonType
|
||||
)
|
||||
{
|
||||
static set<Token> validOperators{
|
||||
Token::BitAnd,
|
||||
Token::BitOr,
|
||||
Token::BitXor,
|
||||
Token::SHL,
|
||||
Token::SHR,
|
||||
Token::SAR
|
||||
};
|
||||
solAssert(validOperators.count(_op), "");
|
||||
solAssert(_commonType, "");
|
||||
|
||||
auto [bvSize, isSigned] = smt::typeBvSizeAndSignedness(_commonType);
|
||||
|
||||
auto bvLeft = smtutil::Expression::int2bv(_left, bvSize);
|
||||
auto bvRight = smtutil::Expression::int2bv(_right, bvSize);
|
||||
|
||||
optional<smtutil::Expression> result;
|
||||
switch (_op)
|
||||
{
|
||||
case Token::BitAnd:
|
||||
result = bvLeft & bvRight;
|
||||
break;
|
||||
case Token::BitOr:
|
||||
result = bvLeft | bvRight;
|
||||
break;
|
||||
case Token::BitXor:
|
||||
result = bvLeft ^ bvRight;
|
||||
break;
|
||||
case Token::SHL:
|
||||
result = bvLeft << bvRight;
|
||||
break;
|
||||
case Token::SHR:
|
||||
solAssert(false, "");
|
||||
case Token::SAR:
|
||||
result = isSigned ?
|
||||
smtutil::Expression::ashr(bvLeft, bvRight) :
|
||||
bvLeft >> bvRight;
|
||||
break;
|
||||
default:
|
||||
solAssert(false, "");
|
||||
}
|
||||
|
||||
solAssert(result.has_value(), "");
|
||||
return smtutil::Expression::bv2int(*result, isSigned);
|
||||
}
|
||||
|
||||
void SMTEncoder::compareOperation(BinaryOperation const& _op)
|
||||
{
|
||||
auto const& commonType = _op.annotation().commonType;
|
||||
solAssert(commonType, "");
|
||||
if (smt::isSupportedType(commonType->category()))
|
||||
if (smt::isSupportedType(*commonType))
|
||||
{
|
||||
smtutil::Expression left(expr(_op.leftExpression(), commonType));
|
||||
smtutil::Expression right(expr(_op.rightExpression(), commonType));
|
||||
Token op = _op.getOperator();
|
||||
shared_ptr<smtutil::Expression> value;
|
||||
if (smt::isNumber(commonType->category()))
|
||||
if (smt::isNumber(*commonType))
|
||||
{
|
||||
value = make_shared<smtutil::Expression>(
|
||||
op == Token::Equal ? (left == right) :
|
||||
@ -1354,7 +1508,7 @@ void SMTEncoder::compareOperation(BinaryOperation const& _op)
|
||||
}
|
||||
else // Bool
|
||||
{
|
||||
solUnimplementedAssert(smt::isBool(commonType->category()), "Operation not yet supported");
|
||||
solUnimplementedAssert(smt::isBool(*commonType), "Operation not yet supported");
|
||||
value = make_shared<smtutil::Expression>(
|
||||
op == Token::Equal ? (left == right) :
|
||||
/*op == Token::NotEqual*/ (left != right)
|
||||
@ -1402,26 +1556,17 @@ void SMTEncoder::booleanOperation(BinaryOperation const& _op)
|
||||
|
||||
void SMTEncoder::bitwiseOperation(BinaryOperation const& _op)
|
||||
{
|
||||
solAssert(TokenTraits::isBitOp(_op.getOperator()), "");
|
||||
auto op = _op.getOperator();
|
||||
solAssert(TokenTraits::isBitOp(op) || TokenTraits::isShiftOp(op), "");
|
||||
auto commonType = _op.annotation().commonType;
|
||||
solAssert(commonType, "");
|
||||
|
||||
auto [bvSize, isSigned] = smt::typeBvSizeAndSignedness(commonType);
|
||||
|
||||
auto bvLeft = smtutil::Expression::int2bv(expr(_op.leftExpression(), commonType), bvSize);
|
||||
auto bvRight = smtutil::Expression::int2bv(expr(_op.rightExpression(), commonType), bvSize);
|
||||
|
||||
optional<smtutil::Expression> result;
|
||||
if (_op.getOperator() == Token::BitAnd)
|
||||
result = bvLeft & bvRight;
|
||||
else if (_op.getOperator() == Token::BitOr)
|
||||
result = bvLeft | bvRight;
|
||||
else if (_op.getOperator() == Token::BitXor)
|
||||
result = bvLeft ^ bvRight;
|
||||
|
||||
solAssert(result, "");
|
||||
if (result)
|
||||
defineExpr(_op, smtutil::Expression::bv2int(*result, isSigned));
|
||||
defineExpr(_op, bitwiseOperation(
|
||||
_op.getOperator(),
|
||||
expr(_op.leftExpression(), commonType),
|
||||
expr(_op.rightExpression(), commonType),
|
||||
commonType
|
||||
));
|
||||
}
|
||||
|
||||
void SMTEncoder::bitwiseNotOperation(UnaryOperation const& _op)
|
||||
@ -1438,11 +1583,7 @@ smtutil::Expression SMTEncoder::division(smtutil::Expression _left, smtutil::Exp
|
||||
{
|
||||
// Signed division in SMTLIB2 rounds differently for negative division.
|
||||
if (_type.isSigned())
|
||||
return (smtutil::Expression::ite(
|
||||
_left >= 0,
|
||||
smtutil::Expression::ite(_right >= 0, _left / _right, 0 - (_left / (0 - _right))),
|
||||
smtutil::Expression::ite(_right >= 0, 0 - ((0 - _left) / _right), (0 - _left) / (0 - _right))
|
||||
));
|
||||
return signedDivisionEVM(_left, _right);
|
||||
else
|
||||
return _left / _right;
|
||||
}
|
||||
@ -1450,8 +1591,7 @@ smtutil::Expression SMTEncoder::division(smtutil::Expression _left, smtutil::Exp
|
||||
void SMTEncoder::assignment(
|
||||
Expression const& _left,
|
||||
smtutil::Expression const& _right,
|
||||
TypePointer const& _type,
|
||||
langutil::SourceLocation const& _location
|
||||
TypePointer const& _type
|
||||
)
|
||||
{
|
||||
solAssert(
|
||||
@ -1461,28 +1601,21 @@ void SMTEncoder::assignment(
|
||||
|
||||
Expression const* left = innermostTuple(_left);
|
||||
|
||||
if (!smt::isSupportedType(_type->category()))
|
||||
if (!smt::isSupportedType(*_type))
|
||||
{
|
||||
// Give it a new index anyway to keep the SSA scheme sound.
|
||||
if (auto varDecl = identifierToVariable(*left))
|
||||
m_context.newValue(*varDecl);
|
||||
|
||||
m_errorReporter.warning(
|
||||
6191_error,
|
||||
_location,
|
||||
"Assertion checker does not yet implement type " + _type->toString()
|
||||
);
|
||||
}
|
||||
else if (auto varDecl = identifierToVariable(*left))
|
||||
assignment(*varDecl, _right);
|
||||
else if (dynamic_cast<IndexAccess const*>(left))
|
||||
arrayIndexAssignment(*left, _right);
|
||||
else if (
|
||||
dynamic_cast<IndexAccess const*>(left) ||
|
||||
dynamic_cast<MemberAccess const*>(left)
|
||||
)
|
||||
indexOrMemberAssignment(*left, _right);
|
||||
else
|
||||
m_errorReporter.warning(
|
||||
8182_error,
|
||||
_location,
|
||||
"Assertion checker does not yet implement such assignments."
|
||||
);
|
||||
solAssert(false, "");
|
||||
}
|
||||
|
||||
void SMTEncoder::tupleAssignment(Expression const& _left, Expression const& _right)
|
||||
@ -1511,7 +1644,7 @@ void SMTEncoder::tupleAssignment(Expression const& _left, Expression const& _rig
|
||||
else
|
||||
{
|
||||
auto type = lExpr.annotation().type;
|
||||
assignment(lExpr, expr(rExpr, type), type, lExpr.location());
|
||||
assignment(lExpr, expr(rExpr, type), type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1528,7 +1661,7 @@ void SMTEncoder::tupleAssignment(Expression const& _left, Expression const& _rig
|
||||
|
||||
for (unsigned i = 0; i < lComponents.size(); ++i)
|
||||
if (auto component = lComponents.at(i); component && rComponents.at(i))
|
||||
assignment(*component, smtutil::Expression::tuple_get(symbRight, i), component->annotation().type, component->location());
|
||||
assignment(*component, smtutil::Expression::tuple_get(symbRight, i), component->annotation().type);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1541,9 +1674,27 @@ smtutil::Expression SMTEncoder::compoundAssignment(Assignment const& _assignment
|
||||
{Token::AssignDiv, Token::Div},
|
||||
{Token::AssignMod, Token::Mod}
|
||||
};
|
||||
static map<Token, Token> const compoundToBitwise{
|
||||
{Token::AssignBitAnd, Token::BitAnd},
|
||||
{Token::AssignBitOr, Token::BitOr},
|
||||
{Token::AssignBitXor, Token::BitXor},
|
||||
{Token::AssignShl, Token::SHL},
|
||||
{Token::AssignShr, Token::SHR},
|
||||
{Token::AssignSar, Token::SAR}
|
||||
};
|
||||
Token op = _assignment.assignmentOperator();
|
||||
solAssert(compoundToArithmetic.count(op), "");
|
||||
solAssert(compoundToArithmetic.count(op) || compoundToBitwise.count(op), "");
|
||||
|
||||
auto decl = identifierToVariable(_assignment.leftHandSide());
|
||||
|
||||
if (compoundToBitwise.count(op))
|
||||
return bitwiseOperation(
|
||||
compoundToBitwise.at(op),
|
||||
decl ? currentValue(*decl) : expr(_assignment.leftHandSide()),
|
||||
expr(_assignment.rightHandSide()),
|
||||
_assignment.annotation().type
|
||||
);
|
||||
|
||||
auto values = arithmeticOperation(
|
||||
compoundToArithmetic.at(op),
|
||||
decl ? currentValue(*decl) : expr(_assignment.leftHandSide()),
|
||||
@ -1561,9 +1712,7 @@ void SMTEncoder::assignment(VariableDeclaration const& _variable, Expression con
|
||||
// This is a special case where the SMT sorts are different.
|
||||
// For now we are unaware of other cases where this happens, but if they do appear
|
||||
// we should extract this into an `implicitConversion` function.
|
||||
if (_variable.type()->category() != Type::Category::Array || _value.annotation().type->category() != Type::Category::StringLiteral)
|
||||
assignment(_variable, expr(_value, _variable.type()));
|
||||
// TODO else { store each string literal byte into the array }
|
||||
assignment(_variable, expr(_value, _variable.type()));
|
||||
}
|
||||
|
||||
void SMTEncoder::assignment(VariableDeclaration const& _variable, smtutil::Expression const& _value)
|
||||
@ -1692,32 +1841,43 @@ void SMTEncoder::resetReferences(VariableDeclaration const& _varDecl)
|
||||
if (_var.isStateVariable() && _varDecl.isStateVariable())
|
||||
return false;
|
||||
|
||||
TypePointer prefix = _var.type();
|
||||
TypePointer originalType = typeWithoutPointer(_varDecl.type());
|
||||
while (
|
||||
prefix->category() == Type::Category::Mapping ||
|
||||
prefix->category() == Type::Category::Array
|
||||
)
|
||||
{
|
||||
if (*originalType == *typeWithoutPointer(prefix))
|
||||
return true;
|
||||
if (prefix->category() == Type::Category::Mapping)
|
||||
{
|
||||
auto mapPrefix = dynamic_cast<MappingType const*>(prefix);
|
||||
solAssert(mapPrefix, "");
|
||||
prefix = mapPrefix->valueType();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto arrayPrefix = dynamic_cast<ArrayType const*>(prefix);
|
||||
solAssert(arrayPrefix, "");
|
||||
prefix = arrayPrefix->baseType();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return sameTypeOrSubtype(_var.type(), _varDecl.type());
|
||||
});
|
||||
}
|
||||
|
||||
void SMTEncoder::resetReferences(TypePointer _type)
|
||||
{
|
||||
m_context.resetVariables([&](VariableDeclaration const& _var) {
|
||||
return sameTypeOrSubtype(_var.type(), _type);
|
||||
});
|
||||
}
|
||||
|
||||
bool SMTEncoder::sameTypeOrSubtype(TypePointer _a, TypePointer _b)
|
||||
{
|
||||
TypePointer prefix = _a;
|
||||
while (
|
||||
prefix->category() == Type::Category::Mapping ||
|
||||
prefix->category() == Type::Category::Array
|
||||
)
|
||||
{
|
||||
if (*typeWithoutPointer(_b) == *typeWithoutPointer(prefix))
|
||||
return true;
|
||||
if (prefix->category() == Type::Category::Mapping)
|
||||
{
|
||||
auto mapPrefix = dynamic_cast<MappingType const*>(prefix);
|
||||
solAssert(mapPrefix, "");
|
||||
prefix = mapPrefix->valueType();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto arrayPrefix = dynamic_cast<ArrayType const*>(prefix);
|
||||
solAssert(arrayPrefix, "");
|
||||
prefix = arrayPrefix->baseType();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TypePointer SMTEncoder::typeWithoutPointer(TypePointer const& _type)
|
||||
{
|
||||
if (auto refType = dynamic_cast<ReferenceType const*>(_type))
|
||||
@ -1999,6 +2159,14 @@ vector<VariableDeclaration const*> SMTEncoder::stateVariablesIncludingInheritedA
|
||||
return stateVariablesIncludingInheritedAndPrivate(dynamic_cast<ContractDefinition const&>(*_function.scope()));
|
||||
}
|
||||
|
||||
SourceUnit const* SMTEncoder::sourceUnitContaining(Scopable const& _scopable)
|
||||
{
|
||||
for (auto const* s = &_scopable; s; s = dynamic_cast<Scopable const*>(s->scope()))
|
||||
if (auto const* source = dynamic_cast<SourceUnit const*>(s->scope()))
|
||||
return source;
|
||||
solAssert(false, "");
|
||||
}
|
||||
|
||||
void SMTEncoder::createReturnedExpressions(FunctionCall const& _funCall)
|
||||
{
|
||||
FunctionDefinition const* funDef = functionCallToDefinition(_funCall);
|
||||
|
@ -65,6 +65,9 @@ public:
|
||||
static std::vector<VariableDeclaration const*> stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract);
|
||||
static std::vector<VariableDeclaration const*> stateVariablesIncludingInheritedAndPrivate(FunctionDefinition const& _function);
|
||||
|
||||
/// @returns the SourceUnit that contains _scopable.
|
||||
static SourceUnit const* sourceUnitContaining(Scopable const& _scopable);
|
||||
|
||||
protected:
|
||||
// TODO: Check that we do not have concurrent reads and writes to a variable,
|
||||
// because the order of expression evaluation is undefined
|
||||
@ -116,6 +119,14 @@ protected:
|
||||
TypePointer const& _commonType,
|
||||
Expression const& _expression
|
||||
);
|
||||
|
||||
smtutil::Expression bitwiseOperation(
|
||||
Token _op,
|
||||
smtutil::Expression const& _left,
|
||||
smtutil::Expression const& _right,
|
||||
TypePointer const& _commonType
|
||||
);
|
||||
|
||||
void compareOperation(BinaryOperation const& _op);
|
||||
void booleanOperation(BinaryOperation const& _op);
|
||||
void bitwiseOperation(BinaryOperation const& _op);
|
||||
@ -126,6 +137,7 @@ protected:
|
||||
void visitAssert(FunctionCall const& _funCall);
|
||||
void visitRequire(FunctionCall const& _funCall);
|
||||
void visitGasLeft(FunctionCall const& _funCall);
|
||||
void visitObjectCreation(FunctionCall const& _funCall);
|
||||
void visitTypeConversion(FunctionCall const& _funCall);
|
||||
void visitFunctionIdentifier(Identifier const& _identifier);
|
||||
|
||||
@ -146,8 +158,8 @@ protected:
|
||||
/// to variable of some SMT array type
|
||||
/// while aliasing is not supported.
|
||||
void arrayAssignment();
|
||||
/// Handles assignment to SMT array index.
|
||||
void arrayIndexAssignment(Expression const& _expr, smtutil::Expression const& _rightHandSide);
|
||||
/// Handles assignments to index or member access.
|
||||
void indexOrMemberAssignment(Expression const& _expr, smtutil::Expression const& _rightHandSide);
|
||||
|
||||
void arrayPush(FunctionCall const& _funCall);
|
||||
void arrayPop(FunctionCall const& _funCall);
|
||||
@ -168,8 +180,7 @@ protected:
|
||||
void assignment(
|
||||
Expression const& _left,
|
||||
smtutil::Expression const& _right,
|
||||
TypePointer const& _type,
|
||||
langutil::SourceLocation const& _location
|
||||
TypePointer const& _type
|
||||
);
|
||||
/// Handle assignments between tuples.
|
||||
void tupleAssignment(Expression const& _left, Expression const& _right);
|
||||
@ -196,8 +207,12 @@ protected:
|
||||
/// Resets all references/pointers that have the same type or have
|
||||
/// a subexpression of the same type as _varDecl.
|
||||
void resetReferences(VariableDeclaration const& _varDecl);
|
||||
/// Resets all references/pointers that have type _type.
|
||||
void resetReferences(TypePointer _type);
|
||||
/// @returns the type without storage pointer information if it has it.
|
||||
TypePointer typeWithoutPointer(TypePointer const& _type);
|
||||
/// @returns whether _a or a subtype of _a is the same as _b.
|
||||
bool sameTypeOrSubtype(TypePointer _a, TypePointer _b);
|
||||
|
||||
/// Given two different branches and the touched variables,
|
||||
/// merge the touched variables into after-branch ite variables
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity::util;
|
||||
using namespace solidity::smtutil;
|
||||
|
||||
namespace solidity::frontend::smt
|
||||
@ -32,7 +33,7 @@ namespace solidity::frontend::smt
|
||||
|
||||
SortPointer smtSort(frontend::Type const& _type)
|
||||
{
|
||||
switch (smtKind(_type.category()))
|
||||
switch (smtKind(_type))
|
||||
{
|
||||
case Kind::Int:
|
||||
if (auto const* intType = dynamic_cast<IntegerType const*>(&_type))
|
||||
@ -63,13 +64,13 @@ SortPointer smtSort(frontend::Type const& _type)
|
||||
case Kind::Array:
|
||||
{
|
||||
shared_ptr<ArraySort> array;
|
||||
if (isMapping(_type.category()))
|
||||
if (isMapping(_type))
|
||||
{
|
||||
auto mapType = dynamic_cast<frontend::MappingType const*>(&_type);
|
||||
solAssert(mapType, "");
|
||||
array = make_shared<ArraySort>(smtSortAbstractFunction(*mapType->keyType()), smtSortAbstractFunction(*mapType->valueType()));
|
||||
}
|
||||
else if (isStringLiteral(_type.category()))
|
||||
else if (isStringLiteral(_type))
|
||||
{
|
||||
auto stringLitType = dynamic_cast<frontend::StringLiteralType const*>(&_type);
|
||||
solAssert(stringLitType, "");
|
||||
@ -130,18 +131,32 @@ SortPointer smtSort(frontend::Type const& _type)
|
||||
}
|
||||
case Kind::Tuple:
|
||||
{
|
||||
auto tupleType = dynamic_cast<frontend::TupleType const*>(&_type);
|
||||
solAssert(tupleType, "");
|
||||
vector<string> members;
|
||||
auto const& tupleName = _type.identifier();
|
||||
auto const& components = tupleType->components();
|
||||
for (unsigned i = 0; i < components.size(); ++i)
|
||||
members.emplace_back(tupleName + "_accessor_" + to_string(i));
|
||||
return make_shared<TupleSort>(
|
||||
tupleName,
|
||||
members,
|
||||
smtSortAbstractFunction(tupleType->components())
|
||||
);
|
||||
auto const& tupleName = _type.toString(true);
|
||||
vector<SortPointer> sorts;
|
||||
|
||||
if (auto const* tupleType = dynamic_cast<frontend::TupleType const*>(&_type))
|
||||
{
|
||||
auto const& components = tupleType->components();
|
||||
for (unsigned i = 0; i < components.size(); ++i)
|
||||
members.emplace_back(tupleName + "_accessor_" + to_string(i));
|
||||
sorts = smtSortAbstractFunction(tupleType->components());
|
||||
}
|
||||
else if (auto const* structType = dynamic_cast<frontend::StructType const*>(&_type))
|
||||
{
|
||||
solAssert(!structType->recursive(), "");
|
||||
auto const& structMembers = structType->structDefinition().members();
|
||||
for (auto member: structMembers)
|
||||
members.emplace_back(tupleName + "_accessor_" + member->name());
|
||||
sorts = smtSortAbstractFunction(applyMap(
|
||||
structMembers,
|
||||
[](auto var) { return var->type(); }
|
||||
));
|
||||
}
|
||||
else
|
||||
solAssert(false, "");
|
||||
|
||||
return make_shared<TupleSort>(tupleName, members, sorts);
|
||||
}
|
||||
default:
|
||||
// Abstract case.
|
||||
@ -159,7 +174,7 @@ vector<SortPointer> smtSort(vector<frontend::TypePointer> const& _types)
|
||||
|
||||
SortPointer smtSortAbstractFunction(frontend::Type const& _type)
|
||||
{
|
||||
if (isFunction(_type.category()))
|
||||
if (isFunction(_type))
|
||||
return SortProvider::uintSort;
|
||||
return smtSort(_type);
|
||||
}
|
||||
@ -175,35 +190,36 @@ vector<SortPointer> smtSortAbstractFunction(vector<frontend::TypePointer> const&
|
||||
return sorts;
|
||||
}
|
||||
|
||||
Kind smtKind(frontend::Type::Category _category)
|
||||
Kind smtKind(frontend::Type const& _type)
|
||||
{
|
||||
if (isNumber(_category))
|
||||
if (isNumber(_type))
|
||||
return Kind::Int;
|
||||
else if (isBool(_category))
|
||||
else if (isBool(_type))
|
||||
return Kind::Bool;
|
||||
else if (isFunction(_category))
|
||||
else if (isFunction(_type))
|
||||
return Kind::Function;
|
||||
else if (isMapping(_category) || isArray(_category))
|
||||
else if (isMapping(_type) || isArray(_type))
|
||||
return Kind::Array;
|
||||
else if (isTuple(_category))
|
||||
else if (isTuple(_type) || isNonRecursiveStruct(_type))
|
||||
return Kind::Tuple;
|
||||
// Abstract case.
|
||||
return Kind::Int;
|
||||
}
|
||||
|
||||
bool isSupportedType(frontend::Type::Category _category)
|
||||
bool isSupportedType(frontend::Type const& _type)
|
||||
{
|
||||
return isNumber(_category) ||
|
||||
isBool(_category) ||
|
||||
isMapping(_category) ||
|
||||
isArray(_category) ||
|
||||
isTuple(_category);
|
||||
return isNumber(_type) ||
|
||||
isBool(_type) ||
|
||||
isMapping(_type) ||
|
||||
isArray(_type) ||
|
||||
isTuple(_type) ||
|
||||
isNonRecursiveStruct(_type);
|
||||
}
|
||||
|
||||
bool isSupportedTypeDeclaration(frontend::Type::Category _category)
|
||||
bool isSupportedTypeDeclaration(frontend::Type const& _type)
|
||||
{
|
||||
return isSupportedType(_category) ||
|
||||
isFunction(_category);
|
||||
return isSupportedType(_type) ||
|
||||
isFunction(_type);
|
||||
}
|
||||
|
||||
pair<bool, shared_ptr<SymbolicVariable>> newSymbolicVariable(
|
||||
@ -220,9 +236,9 @@ pair<bool, shared_ptr<SymbolicVariable>> newSymbolicVariable(
|
||||
abstract = true;
|
||||
var = make_shared<SymbolicIntVariable>(frontend::TypeProvider::uint256(), type, _uniqueName, _context);
|
||||
}
|
||||
else if (isBool(_type.category()))
|
||||
else if (isBool(_type))
|
||||
var = make_shared<SymbolicBoolVariable>(type, _uniqueName, _context);
|
||||
else if (isFunction(_type.category()))
|
||||
else if (isFunction(_type))
|
||||
{
|
||||
auto const& fType = dynamic_cast<FunctionType const*>(type);
|
||||
auto const& paramsIn = fType->parameterTypes();
|
||||
@ -245,21 +261,21 @@ pair<bool, shared_ptr<SymbolicVariable>> newSymbolicVariable(
|
||||
else
|
||||
var = make_shared<SymbolicFunctionVariable>(type, _uniqueName, _context);
|
||||
}
|
||||
else if (isInteger(_type.category()))
|
||||
else if (isInteger(_type))
|
||||
var = make_shared<SymbolicIntVariable>(type, type, _uniqueName, _context);
|
||||
else if (isFixedPoint(_type.category()))
|
||||
else if (isFixedPoint(_type))
|
||||
var = make_shared<SymbolicIntVariable>(type, type, _uniqueName, _context);
|
||||
else if (isFixedBytes(_type.category()))
|
||||
else if (isFixedBytes(_type))
|
||||
{
|
||||
auto fixedBytesType = dynamic_cast<frontend::FixedBytesType const*>(type);
|
||||
solAssert(fixedBytesType, "");
|
||||
var = make_shared<SymbolicFixedBytesVariable>(type, fixedBytesType->numBytes(), _uniqueName, _context);
|
||||
}
|
||||
else if (isAddress(_type.category()) || isContract(_type.category()))
|
||||
else if (isAddress(_type) || isContract(_type))
|
||||
var = make_shared<SymbolicAddressVariable>(_uniqueName, _context);
|
||||
else if (isEnum(_type.category()))
|
||||
else if (isEnum(_type))
|
||||
var = make_shared<SymbolicEnumVariable>(type, _uniqueName, _context);
|
||||
else if (isRational(_type.category()))
|
||||
else if (isRational(_type))
|
||||
{
|
||||
auto rational = dynamic_cast<frontend::RationalNumberType const*>(&_type);
|
||||
solAssert(rational, "");
|
||||
@ -268,106 +284,104 @@ pair<bool, shared_ptr<SymbolicVariable>> newSymbolicVariable(
|
||||
else
|
||||
var = make_shared<SymbolicIntVariable>(type, type, _uniqueName, _context);
|
||||
}
|
||||
else if (isMapping(_type.category()) || isArray(_type.category()))
|
||||
else if (isMapping(_type) || isArray(_type))
|
||||
var = make_shared<SymbolicArrayVariable>(type, type, _uniqueName, _context);
|
||||
else if (isTuple(_type.category()))
|
||||
else if (isTuple(_type))
|
||||
var = make_shared<SymbolicTupleVariable>(type, _uniqueName, _context);
|
||||
else if (isStringLiteral(_type.category()))
|
||||
else if (isStringLiteral(_type))
|
||||
{
|
||||
auto stringType = TypeProvider::stringMemory();
|
||||
var = make_shared<SymbolicArrayVariable>(stringType, type, _uniqueName, _context);
|
||||
}
|
||||
else if (isNonRecursiveStruct(_type))
|
||||
var = make_shared<SymbolicStructVariable>(type, _uniqueName, _context);
|
||||
else
|
||||
solAssert(false, "");
|
||||
return make_pair(abstract, var);
|
||||
}
|
||||
|
||||
bool isSupportedType(frontend::Type const& _type)
|
||||
bool isInteger(frontend::Type const& _type)
|
||||
{
|
||||
return isSupportedType(_type.category());
|
||||
return _type.category() == frontend::Type::Category::Integer;
|
||||
}
|
||||
|
||||
bool isSupportedTypeDeclaration(frontend::Type const& _type)
|
||||
bool isFixedPoint(frontend::Type const& _type)
|
||||
{
|
||||
return isSupportedTypeDeclaration(_type.category());
|
||||
return _type.category() == frontend::Type::Category::FixedPoint;
|
||||
}
|
||||
|
||||
bool isInteger(frontend::Type::Category _category)
|
||||
bool isRational(frontend::Type const& _type)
|
||||
{
|
||||
return _category == frontend::Type::Category::Integer;
|
||||
return _type.category() == frontend::Type::Category::RationalNumber;
|
||||
}
|
||||
|
||||
bool isFixedPoint(frontend::Type::Category _category)
|
||||
bool isFixedBytes(frontend::Type const& _type)
|
||||
{
|
||||
return _category == frontend::Type::Category::FixedPoint;
|
||||
return _type.category() == frontend::Type::Category::FixedBytes;
|
||||
}
|
||||
|
||||
bool isRational(frontend::Type::Category _category)
|
||||
bool isAddress(frontend::Type const& _type)
|
||||
{
|
||||
return _category == frontend::Type::Category::RationalNumber;
|
||||
return _type.category() == frontend::Type::Category::Address;
|
||||
}
|
||||
|
||||
bool isFixedBytes(frontend::Type::Category _category)
|
||||
bool isContract(frontend::Type const& _type)
|
||||
{
|
||||
return _category == frontend::Type::Category::FixedBytes;
|
||||
return _type.category() == frontend::Type::Category::Contract;
|
||||
}
|
||||
|
||||
bool isAddress(frontend::Type::Category _category)
|
||||
bool isEnum(frontend::Type const& _type)
|
||||
{
|
||||
return _category == frontend::Type::Category::Address;
|
||||
return _type.category() == frontend::Type::Category::Enum;
|
||||
}
|
||||
|
||||
bool isContract(frontend::Type::Category _category)
|
||||
bool isNumber(frontend::Type const& _type)
|
||||
{
|
||||
return _category == frontend::Type::Category::Contract;
|
||||
return isInteger(_type) ||
|
||||
isFixedPoint(_type) ||
|
||||
isRational(_type) ||
|
||||
isFixedBytes(_type) ||
|
||||
isAddress(_type) ||
|
||||
isContract(_type) ||
|
||||
isEnum(_type);
|
||||
}
|
||||
|
||||
bool isEnum(frontend::Type::Category _category)
|
||||
bool isBool(frontend::Type const& _type)
|
||||
{
|
||||
return _category == frontend::Type::Category::Enum;
|
||||
return _type.category() == frontend::Type::Category::Bool;
|
||||
}
|
||||
|
||||
bool isNumber(frontend::Type::Category _category)
|
||||
bool isFunction(frontend::Type const& _type)
|
||||
{
|
||||
return isInteger(_category) ||
|
||||
isFixedPoint(_category) ||
|
||||
isRational(_category) ||
|
||||
isFixedBytes(_category) ||
|
||||
isAddress(_category) ||
|
||||
isContract(_category) ||
|
||||
isEnum(_category);
|
||||
return _type.category() == frontend::Type::Category::Function;
|
||||
}
|
||||
|
||||
bool isBool(frontend::Type::Category _category)
|
||||
bool isMapping(frontend::Type const& _type)
|
||||
{
|
||||
return _category == frontend::Type::Category::Bool;
|
||||
return _type.category() == frontend::Type::Category::Mapping;
|
||||
}
|
||||
|
||||
bool isFunction(frontend::Type::Category _category)
|
||||
bool isArray(frontend::Type const& _type)
|
||||
{
|
||||
return _category == frontend::Type::Category::Function;
|
||||
return _type.category() == frontend::Type::Category::Array ||
|
||||
_type.category() == frontend::Type::Category::StringLiteral ||
|
||||
_type.category() == frontend::Type::Category::ArraySlice;
|
||||
}
|
||||
|
||||
bool isMapping(frontend::Type::Category _category)
|
||||
bool isTuple(frontend::Type const& _type)
|
||||
{
|
||||
return _category == frontend::Type::Category::Mapping;
|
||||
return _type.category() == frontend::Type::Category::Tuple;
|
||||
}
|
||||
|
||||
bool isArray(frontend::Type::Category _category)
|
||||
bool isStringLiteral(frontend::Type const& _type)
|
||||
{
|
||||
return _category == frontend::Type::Category::Array ||
|
||||
_category == frontend::Type::Category::StringLiteral ||
|
||||
_category == frontend::Type::Category::ArraySlice;
|
||||
return _type.category() == frontend::Type::Category::StringLiteral;
|
||||
}
|
||||
|
||||
bool isTuple(frontend::Type::Category _category)
|
||||
bool isNonRecursiveStruct(frontend::Type const& _type)
|
||||
{
|
||||
return _category == frontend::Type::Category::Tuple;
|
||||
}
|
||||
|
||||
bool isStringLiteral(frontend::Type::Category _category)
|
||||
{
|
||||
return _category == frontend::Type::Category::StringLiteral;
|
||||
auto structType = dynamic_cast<StructType const*>(&_type);
|
||||
return structType && !structType->recursive();
|
||||
}
|
||||
|
||||
smtutil::Expression minValue(frontend::IntegerType const& _type)
|
||||
@ -394,13 +408,13 @@ void setSymbolicZeroValue(smtutil::Expression _expr, frontend::TypePointer const
|
||||
smtutil::Expression zeroValue(frontend::TypePointer const& _type)
|
||||
{
|
||||
solAssert(_type, "");
|
||||
if (isSupportedType(_type->category()))
|
||||
if (isSupportedType(*_type))
|
||||
{
|
||||
if (isNumber(_type->category()))
|
||||
if (isNumber(*_type))
|
||||
return 0;
|
||||
if (isBool(_type->category()))
|
||||
if (isBool(*_type))
|
||||
return smtutil::Expression(false);
|
||||
if (isArray(_type->category()) || isMapping(_type->category()))
|
||||
if (isArray(*_type) || isMapping(*_type))
|
||||
{
|
||||
auto tupleSort = dynamic_pointer_cast<TupleSort>(smtSort(*_type));
|
||||
solAssert(tupleSort, "");
|
||||
@ -421,11 +435,23 @@ smtutil::Expression zeroValue(frontend::TypePointer const& _type)
|
||||
|
||||
solAssert(zeroArray, "");
|
||||
return smtutil::Expression::tuple_constructor(
|
||||
smtutil::Expression(std::make_shared<SortSort>(smtSort(*_type)), _type->toString(true)),
|
||||
smtutil::Expression(std::make_shared<SortSort>(tupleSort), tupleSort->name),
|
||||
vector<smtutil::Expression>{*zeroArray, length}
|
||||
);
|
||||
|
||||
}
|
||||
if (isNonRecursiveStruct(*_type))
|
||||
{
|
||||
auto const* structType = dynamic_cast<StructType const*>(_type);
|
||||
auto structSort = dynamic_pointer_cast<TupleSort>(smtSort(*_type));
|
||||
return smtutil::Expression::tuple_constructor(
|
||||
smtutil::Expression(make_shared<SortSort>(structSort), structSort->name),
|
||||
applyMap(
|
||||
structType->structDefinition().members(),
|
||||
[](auto var) { return zeroValue(var->type()); }
|
||||
)
|
||||
);
|
||||
}
|
||||
solAssert(false, "");
|
||||
}
|
||||
// Unsupported types are abstracted as Int.
|
||||
@ -452,14 +478,14 @@ void setSymbolicUnknownValue(SymbolicVariable const& _variable, EncodingContext&
|
||||
void setSymbolicUnknownValue(smtutil::Expression _expr, frontend::TypePointer const& _type, EncodingContext& _context)
|
||||
{
|
||||
solAssert(_type, "");
|
||||
if (isEnum(_type->category()))
|
||||
if (isEnum(*_type))
|
||||
{
|
||||
auto enumType = dynamic_cast<frontend::EnumType const*>(_type);
|
||||
solAssert(enumType, "");
|
||||
_context.addAssertion(_expr >= 0);
|
||||
_context.addAssertion(_expr < enumType->numberOfMembers());
|
||||
}
|
||||
else if (isInteger(_type->category()))
|
||||
else if (isInteger(*_type))
|
||||
{
|
||||
auto intType = dynamic_cast<frontend::IntegerType const*>(_type);
|
||||
solAssert(intType, "");
|
||||
|
@ -34,29 +34,30 @@ std::vector<smtutil::SortPointer> smtSort(std::vector<frontend::TypePointer> con
|
||||
smtutil::SortPointer smtSortAbstractFunction(frontend::Type const& _type);
|
||||
std::vector<smtutil::SortPointer> smtSortAbstractFunction(std::vector<frontend::TypePointer> const& _types);
|
||||
/// Returns the SMT kind that models the Solidity type type category _category.
|
||||
smtutil::Kind smtKind(frontend::Type::Category _category);
|
||||
smtutil::Kind smtKind(frontend::Type const& _type);
|
||||
|
||||
/// Returns true if type is fully supported (declaration and operations).
|
||||
bool isSupportedType(frontend::Type::Category _category);
|
||||
bool isSupportedType(frontend::Type const& _type);
|
||||
bool isSupportedType(frontend::Type const& _type);
|
||||
/// Returns true if type is partially supported (declaration).
|
||||
bool isSupportedTypeDeclaration(frontend::Type::Category _category);
|
||||
bool isSupportedTypeDeclaration(frontend::Type const& _type);
|
||||
bool isSupportedTypeDeclaration(frontend::Type const& _type);
|
||||
|
||||
bool isInteger(frontend::Type::Category _category);
|
||||
bool isFixedPoint(frontend::Type::Category _category);
|
||||
bool isRational(frontend::Type::Category _category);
|
||||
bool isFixedBytes(frontend::Type::Category _category);
|
||||
bool isAddress(frontend::Type::Category _category);
|
||||
bool isContract(frontend::Type::Category _category);
|
||||
bool isEnum(frontend::Type::Category _category);
|
||||
bool isNumber(frontend::Type::Category _category);
|
||||
bool isBool(frontend::Type::Category _category);
|
||||
bool isFunction(frontend::Type::Category _category);
|
||||
bool isMapping(frontend::Type::Category _category);
|
||||
bool isArray(frontend::Type::Category _category);
|
||||
bool isTuple(frontend::Type::Category _category);
|
||||
bool isStringLiteral(frontend::Type::Category _category);
|
||||
bool isInteger(frontend::Type const& _type);
|
||||
bool isFixedPoint(frontend::Type const& _type);
|
||||
bool isRational(frontend::Type const& _type);
|
||||
bool isFixedBytes(frontend::Type const& _type);
|
||||
bool isAddress(frontend::Type const& _type);
|
||||
bool isContract(frontend::Type const& _type);
|
||||
bool isEnum(frontend::Type const& _type);
|
||||
bool isNumber(frontend::Type const& _type);
|
||||
bool isBool(frontend::Type const& _type);
|
||||
bool isFunction(frontend::Type const& _type);
|
||||
bool isMapping(frontend::Type const& _type);
|
||||
bool isArray(frontend::Type const& _type);
|
||||
bool isTuple(frontend::Type const& _type);
|
||||
bool isStringLiteral(frontend::Type const& _type);
|
||||
bool isNonRecursiveStruct(frontend::Type const& _type);
|
||||
|
||||
/// Returns a new symbolic variable, according to _type.
|
||||
/// Also returns whether the type is abstract or not,
|
||||
|
@ -21,8 +21,11 @@
|
||||
#include <libsolidity/formal/SymbolicTypes.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
|
||||
#include <libsolutil/Algorithms.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::util;
|
||||
using namespace solidity::smtutil;
|
||||
using namespace solidity::frontend;
|
||||
using namespace solidity::frontend::smt;
|
||||
@ -118,7 +121,7 @@ SymbolicIntVariable::SymbolicIntVariable(
|
||||
):
|
||||
SymbolicVariable(_type, _originalType, move(_uniqueName), _context)
|
||||
{
|
||||
solAssert(isNumber(m_type->category()), "");
|
||||
solAssert(isNumber(*m_type), "");
|
||||
}
|
||||
|
||||
SymbolicAddressVariable::SymbolicAddressVariable(
|
||||
@ -218,7 +221,7 @@ SymbolicEnumVariable::SymbolicEnumVariable(
|
||||
):
|
||||
SymbolicVariable(_type, _type, move(_uniqueName), _context)
|
||||
{
|
||||
solAssert(isEnum(m_type->category()), "");
|
||||
solAssert(isEnum(*m_type), "");
|
||||
}
|
||||
|
||||
SymbolicTupleVariable::SymbolicTupleVariable(
|
||||
@ -228,7 +231,7 @@ SymbolicTupleVariable::SymbolicTupleVariable(
|
||||
):
|
||||
SymbolicVariable(_type, _type, move(_uniqueName), _context)
|
||||
{
|
||||
solAssert(isTuple(m_type->category()), "");
|
||||
solAssert(isTuple(*m_type), "");
|
||||
}
|
||||
|
||||
SymbolicTupleVariable::SymbolicTupleVariable(
|
||||
@ -274,7 +277,7 @@ SymbolicArrayVariable::SymbolicArrayVariable(
|
||||
m_context
|
||||
)
|
||||
{
|
||||
solAssert(isArray(m_type->category()) || isMapping(m_type->category()), "");
|
||||
solAssert(isArray(*m_type) || isMapping(*m_type), "");
|
||||
}
|
||||
|
||||
SymbolicArrayVariable::SymbolicArrayVariable(
|
||||
@ -319,3 +322,47 @@ smtutil::Expression SymbolicArrayVariable::length()
|
||||
{
|
||||
return m_pair.component(1);
|
||||
}
|
||||
|
||||
SymbolicStructVariable::SymbolicStructVariable(
|
||||
frontend::TypePointer _type,
|
||||
string _uniqueName,
|
||||
EncodingContext& _context
|
||||
):
|
||||
SymbolicVariable(_type, _type, move(_uniqueName), _context)
|
||||
{
|
||||
solAssert(isNonRecursiveStruct(*m_type), "");
|
||||
auto const* structType = dynamic_cast<StructType const*>(_type);
|
||||
solAssert(structType, "");
|
||||
auto const& members = structType->structDefinition().members();
|
||||
for (unsigned i = 0; i < members.size(); ++i)
|
||||
{
|
||||
solAssert(members.at(i), "");
|
||||
m_memberIndices.emplace(members.at(i)->name(), i);
|
||||
}
|
||||
}
|
||||
|
||||
smtutil::Expression SymbolicStructVariable::member(string const& _member)
|
||||
{
|
||||
return smtutil::Expression::tuple_get(currentValue(), m_memberIndices.at(_member));
|
||||
}
|
||||
|
||||
smtutil::Expression SymbolicStructVariable::assignMember(string const& _member, smtutil::Expression const& _memberValue)
|
||||
{
|
||||
auto const* structType = dynamic_cast<StructType const*>(m_type);
|
||||
solAssert(structType, "");
|
||||
auto const& structDef = structType->structDefinition();
|
||||
auto const& structMembers = structDef.members();
|
||||
auto oldMembers = applyMap(
|
||||
structMembers,
|
||||
[&](auto _member) { return member(_member->name()); }
|
||||
);
|
||||
increaseIndex();
|
||||
for (unsigned i = 0; i < structMembers.size(); ++i)
|
||||
{
|
||||
auto const& memberName = structMembers.at(i)->name();
|
||||
auto newMember = memberName == _member ? _memberValue : oldMembers.at(i);
|
||||
m_context.addAssertion(member(memberName) == newMember);
|
||||
}
|
||||
|
||||
return currentValue();
|
||||
}
|
||||
|
@ -23,6 +23,8 @@
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
|
||||
#include <libsmtutil/SolverInterface.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
namespace solidity::frontend::smt
|
||||
@ -265,4 +267,29 @@ private:
|
||||
SymbolicTupleVariable m_pair;
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialization of SymbolicVariable for Struct.
|
||||
*/
|
||||
class SymbolicStructVariable: public SymbolicVariable
|
||||
{
|
||||
public:
|
||||
SymbolicStructVariable(
|
||||
frontend::TypePointer _type,
|
||||
std::string _uniqueName,
|
||||
EncodingContext& _context
|
||||
);
|
||||
|
||||
/// @returns the symbolic expression representing _member.
|
||||
smtutil::Expression member(std::string const& _member);
|
||||
|
||||
/// @returns the symbolic expression representing this struct
|
||||
/// with field _member updated.
|
||||
smtutil::Expression assignMember(std::string const& _member, smtutil::Expression const& _memberValue);
|
||||
|
||||
private:
|
||||
std::map<std::string, unsigned> m_memberIndices;
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include <libsolidity/analysis/PostTypeChecker.h>
|
||||
#include <libsolidity/analysis/StaticAnalyzer.h>
|
||||
#include <libsolidity/analysis/SyntaxChecker.h>
|
||||
#include <libsolidity/analysis/Scoper.h>
|
||||
#include <libsolidity/analysis/TypeChecker.h>
|
||||
#include <libsolidity/analysis/ViewPureChecker.h>
|
||||
#include <libsolidity/analysis/ImmutableValidator.h>
|
||||
@ -56,7 +57,9 @@
|
||||
|
||||
#include <libyul/YulString.h>
|
||||
#include <libyul/AsmPrinter.h>
|
||||
#include <libyul/AsmJsonConverter.h>
|
||||
#include <libyul/AssemblyStack.h>
|
||||
#include <libyul/AsmParser.h>
|
||||
|
||||
#include <liblangutil/Scanner.h>
|
||||
#include <liblangutil/SemVerHandler.h>
|
||||
@ -297,6 +300,10 @@ bool CompilerStack::analyze()
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must call analyze only after parsing was performed."));
|
||||
resolveImports();
|
||||
|
||||
for (Source const* source: m_sourceOrder)
|
||||
if (source->ast)
|
||||
Scoper::assignScopes(*source->ast);
|
||||
|
||||
bool noErrors = true;
|
||||
|
||||
try
|
||||
@ -359,12 +366,10 @@ bool CompilerStack::analyze()
|
||||
// This also calculates whether a contract is abstract, which is needed by the
|
||||
// type checker.
|
||||
ContractLevelChecker contractLevelChecker(m_errorReporter);
|
||||
|
||||
for (Source const* source: m_sourceOrder)
|
||||
if (source->ast)
|
||||
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
|
||||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
|
||||
if (!contractLevelChecker.check(*contract))
|
||||
noErrors = false;
|
||||
if (auto sourceAst = source->ast)
|
||||
noErrors = contractLevelChecker.check(*sourceAst);
|
||||
|
||||
// Requires ContractLevelChecker
|
||||
DocStringAnalyser docStringAnalyser(m_errorReporter);
|
||||
@ -517,6 +522,10 @@ bool CompilerStack::compile()
|
||||
{
|
||||
if (m_generateEvmBytecode)
|
||||
compileContract(*contract, otherCompilers);
|
||||
if (m_generateIR || m_generateEwasm)
|
||||
generateIR(*contract);
|
||||
if (m_generateEwasm)
|
||||
generateEwasm(*contract);
|
||||
}
|
||||
catch (Error const& _error)
|
||||
{
|
||||
@ -525,10 +534,28 @@ bool CompilerStack::compile()
|
||||
m_errorReporter.error(_error.errorId(), _error.type(), SourceLocation(), _error.what());
|
||||
return false;
|
||||
}
|
||||
if (m_generateIR || m_generateEwasm)
|
||||
generateIR(*contract);
|
||||
if (m_generateEwasm)
|
||||
generateEwasm(*contract);
|
||||
catch (UnimplementedFeatureError const& _unimplementedError)
|
||||
{
|
||||
if (
|
||||
SourceLocation const* sourceLocation =
|
||||
boost::get_error_info<langutil::errinfo_sourceLocation>(_unimplementedError)
|
||||
)
|
||||
{
|
||||
string const* comment = _unimplementedError.comment();
|
||||
m_errorReporter.error(
|
||||
1834_error,
|
||||
Error::Type::CodeGenerationError,
|
||||
*sourceLocation,
|
||||
"Unimplemented feature error" +
|
||||
((comment && !comment->empty()) ? ": " + *comment : string{}) +
|
||||
" in " +
|
||||
_unimplementedError.lineInfo()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
throw;
|
||||
}
|
||||
}
|
||||
m_stackState = CompilationSuccessful;
|
||||
this->link();
|
||||
@ -586,6 +613,48 @@ evmasm::AssemblyItems const* CompilerStack::runtimeAssemblyItems(string const& _
|
||||
return currentContract.compiler ? &contract(_contractName).compiler->runtimeAssemblyItems() : nullptr;
|
||||
}
|
||||
|
||||
Json::Value CompilerStack::generatedSources(string const& _contractName, bool _runtime) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
|
||||
Contract const& c = contract(_contractName);
|
||||
util::LazyInit<Json::Value const> const& sources =
|
||||
_runtime ?
|
||||
c.runtimeGeneratedSources :
|
||||
c.generatedSources;
|
||||
return sources.init([&]{
|
||||
Json::Value sources{Json::arrayValue};
|
||||
// If there is no compiler, then no bytecode was generated and thus no
|
||||
// sources were generated.
|
||||
if (c.compiler)
|
||||
{
|
||||
string source =
|
||||
_runtime ?
|
||||
c.compiler->runtimeGeneratedYulUtilityCode() :
|
||||
c.compiler->generatedYulUtilityCode();
|
||||
if (!source.empty())
|
||||
{
|
||||
string sourceName = CompilerContext::yulUtilityFileName();
|
||||
unsigned sourceIndex = sourceIndices()[sourceName];
|
||||
ErrorList errors;
|
||||
ErrorReporter errorReporter(errors);
|
||||
auto scanner = make_shared<langutil::Scanner>(langutil::CharStream(source, sourceName));
|
||||
yul::EVMDialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(m_evmVersion);
|
||||
shared_ptr<yul::Block> parserResult = yul::Parser{errorReporter, dialect}.parse(scanner, false);
|
||||
solAssert(parserResult, "");
|
||||
sources[0]["ast"] = yul::AsmJsonConverter{sourceIndex}(*parserResult);
|
||||
sources[0]["name"] = sourceName;
|
||||
sources[0]["id"] = sourceIndex;
|
||||
sources[0]["language"] = "Yul";
|
||||
sources[0]["contents"] = move(source);
|
||||
|
||||
}
|
||||
}
|
||||
return sources;
|
||||
});
|
||||
}
|
||||
|
||||
string const* CompilerStack::sourceMapping(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
@ -728,6 +797,8 @@ map<string, unsigned> CompilerStack::sourceIndices() const
|
||||
unsigned index = 0;
|
||||
for (auto const& s: m_sources)
|
||||
indices[s.first] = index++;
|
||||
solAssert(!indices.count(CompilerContext::yulUtilityFileName()), "");
|
||||
indices[CompilerContext::yulUtilityFileName()] = index++;
|
||||
return indices;
|
||||
}
|
||||
|
||||
@ -1024,7 +1095,7 @@ void CompilerStack::resolveImports()
|
||||
for (ASTPointer<ASTNode> const& node: _source->ast->nodes())
|
||||
if (ImportDirective const* import = dynamic_cast<ImportDirective*>(node.get()))
|
||||
{
|
||||
string const& path = import->annotation().absolutePath;
|
||||
string const& path = *import->annotation().absolutePath;
|
||||
solAssert(m_sources.count(path), "");
|
||||
import->annotation().sourceUnit = m_sources[path].ast.get();
|
||||
toposort(&m_sources[path]);
|
||||
@ -1222,9 +1293,9 @@ string CompilerStack::createMetadata(Contract const& _contract) const
|
||||
|
||||
/// All the source files (including self), which should be included in the metadata.
|
||||
set<string> referencedSources;
|
||||
referencedSources.insert(_contract.contract->sourceUnit().annotation().path);
|
||||
referencedSources.insert(*_contract.contract->sourceUnit().annotation().path);
|
||||
for (auto const sourceUnit: _contract.contract->sourceUnit().referencedSourceUnits(true))
|
||||
referencedSources.insert(sourceUnit->annotation().path);
|
||||
referencedSources.insert(*sourceUnit->annotation().path);
|
||||
|
||||
meta["sources"] = Json::objectValue;
|
||||
for (auto const& s: m_sources)
|
||||
@ -1290,7 +1361,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const
|
||||
|
||||
meta["settings"]["evmVersion"] = m_evmVersion.name();
|
||||
meta["settings"]["compilationTarget"][_contract.contract->sourceUnitName()] =
|
||||
_contract.contract->annotation().canonicalName;
|
||||
*_contract.contract->annotation().canonicalName;
|
||||
|
||||
meta["settings"]["remappings"] = Json::arrayValue;
|
||||
set<string> remappings;
|
||||
|
@ -277,6 +277,10 @@ public:
|
||||
/// @returns runtime contract assembly items
|
||||
evmasm::AssemblyItems const* runtimeAssemblyItems(std::string const& _contractName) const;
|
||||
|
||||
/// @returns an array containing all utility sources generated during compilation.
|
||||
/// Format: [ { name: string, id: number, language: "Yul", contents: string }, ... ]
|
||||
Json::Value generatedSources(std::string const& _contractName, bool _runtime = false) const;
|
||||
|
||||
/// @returns the string that provides a mapping between bytecode and sourcecode or a nullptr
|
||||
/// if the contract does not (yet) have bytecode.
|
||||
std::string const* sourceMapping(std::string const& _contractName) const;
|
||||
@ -356,6 +360,8 @@ private:
|
||||
util::LazyInit<Json::Value const> storageLayout;
|
||||
util::LazyInit<Json::Value const> userDocumentation;
|
||||
util::LazyInit<Json::Value const> devDocumentation;
|
||||
util::LazyInit<Json::Value const> generatedSources;
|
||||
util::LazyInit<Json::Value const> runtimeGeneratedSources;
|
||||
mutable std::optional<std::string const> sourceMapping;
|
||||
mutable std::optional<std::string const> runtimeSourceMapping;
|
||||
};
|
||||
|
@ -32,7 +32,7 @@ namespace solidity::frontend
|
||||
struct OptimiserSettings
|
||||
{
|
||||
static char constexpr DefaultYulOptimiserSteps[] =
|
||||
"dhfoDgvulfnTUtnIf" // None of these can make stack problems worse
|
||||
"NdhfoDgvulfnTUtnIf" // None of these can make stack problems worse
|
||||
"["
|
||||
"xarrscLM" // Turn into SSA and simplify
|
||||
"cCTUtTOntnfDIul" // Perform structural simplification
|
||||
@ -41,13 +41,13 @@ struct OptimiserSettings
|
||||
|
||||
// should have good "compilability" property here.
|
||||
|
||||
"eul" // Run functional expression inliner
|
||||
"Tpeul" // Run functional expression inliner
|
||||
"xarulrul" // Prune a bit more in SSA
|
||||
"xarrcL" // Turn into SSA again and simplify
|
||||
"gvif" // Run full inliner
|
||||
"CTUcarrLsTOtfDncarrIulc" // SSA plus simplify
|
||||
"]"
|
||||
"jmuljuljul VcTOcul jmul"; // Make source short and pretty
|
||||
"jmuljuljul VcTOcul jmulN"; // Make source short and pretty
|
||||
|
||||
/// No optimisations at all - not recommended.
|
||||
static OptimiserSettings none()
|
||||
|
@ -230,6 +230,16 @@ bool isArtifactRequested(Json::Value const& _outputSelection, string const& _fil
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @returns all artifact names of the EVM object, either for creation or deploy time.
|
||||
vector<string> evmObjectComponents(string const& _objectKind)
|
||||
{
|
||||
solAssert(_objectKind == "bytecode" || _objectKind == "deployedBytecode", "");
|
||||
vector<string> components{"", ".object", ".opcodes", ".sourceMap", ".generatedSources", ".linkReferences"};
|
||||
if (_objectKind == "deployedBytecode")
|
||||
components.push_back(".immutableReferences");
|
||||
return util::applyMap(components, [&](auto const& _s) { return "evm." + _objectKind + _s; });
|
||||
}
|
||||
|
||||
/// @returns true if any binary was requested, i.e. we actually have to perform compilation.
|
||||
bool isBinaryRequested(Json::Value const& _outputSelection)
|
||||
{
|
||||
@ -237,17 +247,12 @@ bool isBinaryRequested(Json::Value const& _outputSelection)
|
||||
return false;
|
||||
|
||||
// This does not include "evm.methodIdentifiers" on purpose!
|
||||
static vector<string> const outputsThatRequireBinaries{
|
||||
static vector<string> const outputsThatRequireBinaries = vector<string>{
|
||||
"*",
|
||||
"ir", "irOptimized",
|
||||
"wast", "wasm", "ewasm.wast", "ewasm.wasm",
|
||||
"evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes",
|
||||
"evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences",
|
||||
"evm.deployedBytecode.immutableReferences",
|
||||
"evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap",
|
||||
"evm.bytecode.linkReferences",
|
||||
"evm.gasEstimates", "evm.legacyAssembly", "evm.assembly"
|
||||
};
|
||||
} + evmObjectComponents("bytecode") + evmObjectComponents("deployedBytecode");
|
||||
|
||||
for (auto const& fileRequests: _outputSelection)
|
||||
for (auto const& requests: fileRequests)
|
||||
@ -263,15 +268,10 @@ bool isEvmBytecodeRequested(Json::Value const& _outputSelection)
|
||||
if (!_outputSelection.isObject())
|
||||
return false;
|
||||
|
||||
static vector<string> const outputsThatRequireEvmBinaries{
|
||||
static vector<string> const outputsThatRequireEvmBinaries = vector<string>{
|
||||
"*",
|
||||
"evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes",
|
||||
"evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences",
|
||||
"evm.deployedBytecode.immutableReferences",
|
||||
"evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap",
|
||||
"evm.bytecode.linkReferences",
|
||||
"evm.gasEstimates", "evm.legacyAssembly", "evm.assembly"
|
||||
};
|
||||
} + evmObjectComponents("bytecode") + evmObjectComponents("deployedBytecode");
|
||||
|
||||
for (auto const& fileRequests: _outputSelection)
|
||||
for (auto const& requests: fileRequests)
|
||||
@ -364,7 +364,12 @@ Json::Value formatImmutableReferences(map<u256, pair<string, vector<size_t>>> co
|
||||
return ret;
|
||||
}
|
||||
|
||||
Json::Value collectEVMObject(evmasm::LinkerObject const& _object, string const* _sourceMap, bool _runtimeObject)
|
||||
Json::Value collectEVMObject(
|
||||
evmasm::LinkerObject const& _object,
|
||||
string const* _sourceMap,
|
||||
Json::Value _generatedSources,
|
||||
bool _runtimeObject
|
||||
)
|
||||
{
|
||||
Json::Value output = Json::objectValue;
|
||||
output["object"] = _object.toHex();
|
||||
@ -373,6 +378,7 @@ Json::Value collectEVMObject(evmasm::LinkerObject const& _object, string const*
|
||||
output["linkReferences"] = formatLinkReferences(_object.linkReferences);
|
||||
if (_runtimeObject)
|
||||
output["immutableReferences"] = formatImmutableReferences(_object.immutableReferences);
|
||||
output["generatedSources"] = move(_generatedSources);
|
||||
return output;
|
||||
}
|
||||
|
||||
@ -1074,12 +1080,13 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
|
||||
_inputsAndSettings.outputSelection,
|
||||
file,
|
||||
name,
|
||||
{ "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" },
|
||||
evmObjectComponents("bytecode"),
|
||||
wildcardMatchesExperimental
|
||||
))
|
||||
evmData["bytecode"] = collectEVMObject(
|
||||
compilerStack.object(contractName),
|
||||
compilerStack.sourceMapping(contractName),
|
||||
compilerStack.generatedSources(contractName),
|
||||
false
|
||||
);
|
||||
|
||||
@ -1087,12 +1094,13 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
|
||||
_inputsAndSettings.outputSelection,
|
||||
file,
|
||||
name,
|
||||
{ "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences", "evm.deployedBytecode.immutableReferences" },
|
||||
evmObjectComponents("deployedBytecode"),
|
||||
wildcardMatchesExperimental
|
||||
))
|
||||
evmData["deployedBytecode"] = collectEVMObject(
|
||||
compilerStack.runtimeObject(contractName),
|
||||
compilerStack.runtimeSourceMapping(contractName),
|
||||
compilerStack.generatedSources(contractName, true),
|
||||
true
|
||||
);
|
||||
|
||||
@ -1176,24 +1184,19 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
|
||||
tie(object, runtimeObject) = stack.assembleAndGuessRuntime();
|
||||
|
||||
for (string const& objectKind: vector<string>{"bytecode", "deployedBytecode"})
|
||||
{
|
||||
auto artifacts = util::applyMap(
|
||||
vector<string>{"", ".object", ".opcodes", ".sourceMap", ".linkReferences"},
|
||||
[&](auto const& _s) { return "evm." + objectKind + _s; }
|
||||
);
|
||||
if (isArtifactRequested(
|
||||
_inputsAndSettings.outputSelection,
|
||||
sourceName,
|
||||
contractName,
|
||||
artifacts,
|
||||
evmObjectComponents(objectKind),
|
||||
wildcardMatchesExperimental
|
||||
))
|
||||
{
|
||||
MachineAssemblyObject const& o = objectKind == "bytecode" ? object : runtimeObject;
|
||||
if (o.bytecode)
|
||||
output["contracts"][sourceName][contractName]["evm"][objectKind] = collectEVMObject(*o.bytecode, o.sourceMappings.get(), false);
|
||||
output["contracts"][sourceName][contractName]["evm"][objectKind] =
|
||||
collectEVMObject(*o.bytecode, o.sourceMappings.get(), Json::arrayValue, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesExperimental))
|
||||
output["contracts"][sourceName][contractName]["irOptimized"] = stack.print();
|
||||
|
@ -209,6 +209,13 @@ std::map<V, K> invertMap(std::map<K, V> const& originalMap)
|
||||
return inverseMap;
|
||||
}
|
||||
|
||||
/// Returns a set of keys of a map.
|
||||
template <typename K, typename V>
|
||||
std::set<K> keys(std::map<K, V> const& _map)
|
||||
{
|
||||
return applyMap(_map, [](auto const& _elem) { return _elem.first; }, std::set<K>{});
|
||||
}
|
||||
|
||||
// String conversion functions, mainly to/from hex/nibble/byte representations.
|
||||
|
||||
enum class WhenError
|
||||
|
@ -79,6 +79,8 @@ public:
|
||||
/// @throws BadSetOnceAccess when the stored value has not yet been set
|
||||
T const* operator->() const { return std::addressof(**this); }
|
||||
|
||||
/// @return true if a value was assigned
|
||||
bool set() const { return m_value.has_value(); }
|
||||
private:
|
||||
std::optional<T> m_value = std::nullopt;
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user