mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge remote-tracking branch 'origin/develop' into breaking
This commit is contained in:
commit
8bcbe946c6
@ -18,7 +18,7 @@ else {
|
||||
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" ..
|
||||
..\deps\cmake\bin\cmake -G "Visual Studio 16 2019" -DBoost_DIR="$boost_dir\" -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DCMAKE_INSTALL_PREFIX="$PSScriptRoot\..\upload" -DUSE_Z3=OFF ..
|
||||
if ( -not $? ) { throw "CMake configure failed." }
|
||||
msbuild solidity.sln /p:Configuration=Release /m:5 /v:minimal
|
||||
if ( -not $? ) { throw "Build failed." }
|
||||
|
@ -17,12 +17,15 @@ parameters:
|
||||
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:61232feea23c8c57e82cf5fae890f8b86bbec353cdc04f2fcba383ca589e1d8b"
|
||||
ubuntu-1604-clang-ossfuzz-docker-image:
|
||||
type: string
|
||||
# solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-11
|
||||
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:4acb2674eab3e7939d6dc6caa0b8320f4dd79484325242b58473ca2875792d90"
|
||||
# solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-13
|
||||
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:c26a7ffc9fc243a4ec3105b9dc1edcdd964ad0e9665c83172b7ebda74bbf3021"
|
||||
emscripten-docker-image:
|
||||
type: string
|
||||
# solbuildpackpusher/solidity-buildpack-deps:emscripten-6
|
||||
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:092da5817bc032c91a806b4f73db2a1a31e5cc4c066d94d43eedd9f365df7154"
|
||||
evm-version:
|
||||
type: string
|
||||
default: london
|
||||
|
||||
orbs:
|
||||
win: circleci/windows@2.2.0
|
||||
@ -50,6 +53,25 @@ defaults:
|
||||
name: Correctness proofs for optimization rules
|
||||
command: scripts/run_proofs.sh
|
||||
|
||||
- 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
|
||||
name: docs pragma version check
|
||||
command: ./scripts/docs_version_pragma_check.sh
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Artifacts Templates
|
||||
|
||||
@ -111,17 +133,7 @@ defaults:
|
||||
- store_test_results: &store_test_results
|
||||
path: test_results/
|
||||
|
||||
- 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_soltest_steps: &run_soltest_steps
|
||||
- steps_soltest: &steps_soltest
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
@ -130,7 +142,7 @@ defaults:
|
||||
- store_test_results: *store_test_results
|
||||
- store_artifacts: *artifacts_test_results
|
||||
|
||||
- run_soltest_all_steps: &run_soltest_all_steps
|
||||
- steps_soltest_all: &steps_soltest_all
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
@ -139,12 +151,7 @@ defaults:
|
||||
- store_test_results: *store_test_results
|
||||
- store_artifacts: *artifacts_test_results
|
||||
|
||||
- run_cmdline_tests: &run_cmdline_tests
|
||||
name: command line tests
|
||||
no_output_timeout: 30m
|
||||
command: ./test/cmdlineTests.sh
|
||||
|
||||
- run_cmdline_tests_steps: &run_cmdline_tests_steps
|
||||
- steps_cmdline_tests: &steps_cmdline_tests
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
@ -153,34 +160,30 @@ defaults:
|
||||
- store_test_results: *store_test_results
|
||||
- store_artifacts: *artifacts_test_results
|
||||
|
||||
- run_docs_pragma_min_version: &run_docs_pragma_min_version
|
||||
name: docs pragma version check
|
||||
command: ./scripts/docs_version_pragma_check.sh
|
||||
|
||||
- test_ubuntu1604_clang: &test_ubuntu1604_clang
|
||||
docker:
|
||||
- image: << pipeline.parameters.ubuntu-1604-clang-ossfuzz-docker-image >>
|
||||
<<: *run_soltest_steps
|
||||
<<: *steps_soltest
|
||||
|
||||
- test_ubuntu2004_clang: &test_ubuntu2004_clang
|
||||
docker:
|
||||
- image: << pipeline.parameters.ubuntu-2004-clang-docker-image >>
|
||||
<<: *run_soltest_steps
|
||||
<<: *steps_soltest
|
||||
|
||||
- test_ubuntu2004: &test_ubuntu2004
|
||||
docker:
|
||||
- image: << pipeline.parameters.ubuntu-2004-docker-image >>
|
||||
parallelism: 6
|
||||
<<: *run_soltest_all_steps
|
||||
<<: *steps_soltest_all
|
||||
|
||||
- test_asan: &test_asan
|
||||
<<: *test_ubuntu2004
|
||||
<<: *run_soltest_steps
|
||||
<<: *steps_soltest
|
||||
|
||||
- test_ubuntu2004_clang_cli: &test_ubuntu2004_clang_cli
|
||||
docker:
|
||||
- image: << pipeline.parameters.ubuntu-2004-clang-docker-image >>
|
||||
<<: *run_cmdline_tests_steps
|
||||
<<: *steps_cmdline_tests
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Workflow Templates
|
||||
@ -434,6 +437,32 @@ jobs:
|
||||
name: Python unit tests
|
||||
command: python.exe test/pyscriptTests.py
|
||||
|
||||
b_ubu: &b_ubu
|
||||
resource_class: xlarge
|
||||
docker:
|
||||
- image: << pipeline.parameters.ubuntu-2004-docker-image >>
|
||||
environment:
|
||||
MAKEFLAGS: -j 10
|
||||
steps:
|
||||
- checkout
|
||||
- run: *run_build
|
||||
- store_artifacts: *artifacts_solc
|
||||
- store_artifacts: *artifacts_tools
|
||||
- persist_to_workspace: *artifacts_executables
|
||||
|
||||
# x64 ASAN build, for testing for memory related bugs
|
||||
b_ubu_asan: &b_ubu_asan
|
||||
<<: *b_ubu
|
||||
environment:
|
||||
CMAKE_OPTIONS: -DSANITIZE=address
|
||||
MAKEFLAGS: -j 10
|
||||
CMAKE_BUILD_TYPE: Release
|
||||
steps:
|
||||
- checkout
|
||||
- run: *run_build
|
||||
- store_artifacts: *artifacts_solc
|
||||
- persist_to_workspace: *artifacts_executables
|
||||
|
||||
b_ubu_clang: &b_ubu_clang
|
||||
resource_class: xlarge
|
||||
docker:
|
||||
@ -477,19 +506,6 @@ jobs:
|
||||
- store_artifacts: *artifacts_solc
|
||||
- persist_to_workspace: *artifacts_executables
|
||||
|
||||
b_ubu: &b_ubu
|
||||
resource_class: xlarge
|
||||
docker:
|
||||
- image: << pipeline.parameters.ubuntu-2004-docker-image >>
|
||||
environment:
|
||||
MAKEFLAGS: -j 10
|
||||
steps:
|
||||
- checkout
|
||||
- run: *run_build
|
||||
- store_artifacts: *artifacts_solc
|
||||
- store_artifacts: *artifacts_tools
|
||||
- persist_to_workspace: *artifacts_executables
|
||||
|
||||
b_ubu_release: &b_ubu_release
|
||||
<<: *b_ubu
|
||||
environment:
|
||||
@ -523,7 +539,7 @@ jobs:
|
||||
t_ubu_codecov:
|
||||
<<: *test_ubuntu2004
|
||||
environment:
|
||||
EVM: constantinople
|
||||
EVM: << pipeline.parameters.evm-version >>
|
||||
OPTIMIZE: 1
|
||||
steps:
|
||||
- checkout
|
||||
@ -586,9 +602,7 @@ jobs:
|
||||
|
||||
b_archlinux:
|
||||
docker:
|
||||
# FIXME: Newer releases won't work until CircleCI updates its host machines.
|
||||
# See https://github.com/ethereum/solidity/pull/11332
|
||||
- image: archlinux:base-20210131.0.14634
|
||||
- image: archlinux:base
|
||||
environment:
|
||||
TERM: xterm
|
||||
MAKEFLAGS: -j 3
|
||||
@ -642,7 +656,7 @@ jobs:
|
||||
macos:
|
||||
xcode: "11.0.0"
|
||||
environment:
|
||||
EVM: constantinople
|
||||
EVM: << pipeline.parameters.evm-version >>
|
||||
OPTIMIZE: 0
|
||||
TERM: xterm
|
||||
steps:
|
||||
@ -696,19 +710,6 @@ jobs:
|
||||
- soljson.js
|
||||
- version.txt
|
||||
|
||||
# x64 ASAN build, for testing for memory related bugs
|
||||
b_ubu_asan: &b_ubu_asan
|
||||
<<: *b_ubu
|
||||
environment:
|
||||
CMAKE_OPTIONS: -DSANITIZE=address
|
||||
MAKEFLAGS: -j 10
|
||||
CMAKE_BUILD_TYPE: Release
|
||||
steps:
|
||||
- checkout
|
||||
- run: *run_build
|
||||
- store_artifacts: *artifacts_solc
|
||||
- persist_to_workspace: *artifacts_executables
|
||||
|
||||
b_docs:
|
||||
docker:
|
||||
- image: << pipeline.parameters.ubuntu-2004-docker-image >>
|
||||
@ -727,52 +728,38 @@ jobs:
|
||||
|
||||
t_archlinux_soltest: &t_archlinux_soltest
|
||||
docker:
|
||||
# FIXME: Newer releases won't work until CircleCI updates its host machines.
|
||||
# See https://github.com/ethereum/solidity/pull/11332
|
||||
- image: archlinux:base-20210131.0.14634
|
||||
- image: archlinux:base
|
||||
environment:
|
||||
EVM: constantinople
|
||||
EVM: << pipeline.parameters.evm-version >>
|
||||
OPTIMIZE: 0
|
||||
TERM: xterm
|
||||
# For Archlinux we do not have prebuilt docker images and we would need to build evmone from source,
|
||||
# thus we forgo semantics tests to speed things up.
|
||||
# FIXME: Z3 4.8.11 prerelease is now in main Arch Linux repos but it makes some of our SMT
|
||||
# tests hang. Disabling SMT tests until we get a proper release.
|
||||
# See https://github.com/Z3Prover/z3/issues/5330 for more details.
|
||||
SOLTEST_FLAGS: --no-semantic-tests --no-smt
|
||||
SOLTEST_FLAGS: --no-semantic-tests
|
||||
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
|
||||
- when:
|
||||
condition: true
|
||||
<<: *steps_soltest
|
||||
|
||||
t_ubu_soltest_enforce_yul: &t_ubu_soltest_enforce_yul
|
||||
docker:
|
||||
- image: << pipeline.parameters.ubuntu-2004-docker-image >>
|
||||
environment:
|
||||
EVM: constantinople
|
||||
EVM: << pipeline.parameters.evm-version >>
|
||||
SOLTEST_FLAGS: --enforce-via-yul
|
||||
OPTIMIZE: 0
|
||||
TERM: xterm
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: build
|
||||
- run: *run_soltest
|
||||
- store_test_results: *store_test_results
|
||||
- store_artifacts: *artifacts_test_results
|
||||
<<: *steps_soltest
|
||||
|
||||
|
||||
t_ubu_clang_soltest: &t_ubu_clang_soltest
|
||||
<<: *test_ubuntu2004_clang
|
||||
environment:
|
||||
EVM: constantinople
|
||||
EVM: << pipeline.parameters.evm-version >>
|
||||
OPTIMIZE: 0
|
||||
|
||||
t_ubu_release_soltest: &t_ubu_release_soltest
|
||||
@ -783,7 +770,7 @@ jobs:
|
||||
- image: << pipeline.parameters.ubuntu-2004-docker-image >>
|
||||
environment:
|
||||
TERM: xterm
|
||||
<<: *run_cmdline_tests_steps
|
||||
<<: *steps_cmdline_tests
|
||||
|
||||
t_ubu_release_cli: &t_ubu_release_cli
|
||||
<<: *t_ubu_cli
|
||||
@ -793,47 +780,43 @@ jobs:
|
||||
environment:
|
||||
TERM: xterm
|
||||
ASAN_OPTIONS: check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2
|
||||
<<: *run_cmdline_tests_steps
|
||||
<<: *steps_cmdline_tests
|
||||
|
||||
t_ubu_asan_constantinople:
|
||||
t_ubu_asan:
|
||||
<<: *test_asan
|
||||
environment:
|
||||
EVM: constantinople
|
||||
EVM: << pipeline.parameters.evm-version >>
|
||||
OPTIMIZE: 0
|
||||
SOLTEST_FLAGS: --no-smt
|
||||
ASAN_OPTIONS: check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2
|
||||
|
||||
t_ubu_asan_constantinople_clang:
|
||||
t_ubu_asan_clang:
|
||||
<<: *test_ubuntu2004_clang
|
||||
environment:
|
||||
EVM: constantinople
|
||||
EVM: << pipeline.parameters.evm-version >>
|
||||
OPTIMIZE: 0
|
||||
SOLTEST_FLAGS: --no-smt
|
||||
ASAN_OPTIONS: check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2
|
||||
|
||||
t_ubu_ubsan_clang:
|
||||
environment:
|
||||
EVM: << pipeline.parameters.evm-version >>
|
||||
docker:
|
||||
- image: << pipeline.parameters.ubuntu-2004-clang-docker-image >>
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: build
|
||||
- run: *run_soltest
|
||||
- when:
|
||||
condition: true
|
||||
<<: *steps_soltest
|
||||
- run: *gitter_notify_failure
|
||||
- store_test_results: *store_test_results
|
||||
- store_artifacts: *artifacts_test_results
|
||||
|
||||
t_ubu_ubsan_clang_cli:
|
||||
docker:
|
||||
- image: << pipeline.parameters.ubuntu-2004-clang-docker-image >>
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: build
|
||||
- run: *run_cmdline_tests
|
||||
- when:
|
||||
condition: true
|
||||
<<: *steps_cmdline_tests
|
||||
- run: *gitter_notify_failure
|
||||
- store_test_results: *store_test_results
|
||||
- store_artifacts: *artifacts_test_results
|
||||
|
||||
t_ems_solcjs:
|
||||
docker:
|
||||
@ -1024,7 +1007,7 @@ jobs:
|
||||
|
||||
b_bytecode_ems:
|
||||
docker:
|
||||
- image: circleci/node:14
|
||||
- image: circleci/node:16
|
||||
environment:
|
||||
SOLC_EMSCRIPTEN: "On"
|
||||
steps:
|
||||
@ -1216,7 +1199,6 @@ workflows:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
- develop_060
|
||||
|
||||
jobs:
|
||||
# OSSFUZZ builds and (regression) tests
|
||||
@ -1230,8 +1212,8 @@ workflows:
|
||||
# ASan build and tests
|
||||
- b_ubu_asan: *workflow_trigger_on_tags
|
||||
- b_ubu_asan_clang: *workflow_trigger_on_tags
|
||||
- t_ubu_asan_constantinople: *workflow_ubuntu2004_asan
|
||||
- t_ubu_asan_constantinople_clang: *workflow_ubuntu2004_asan_clang
|
||||
- t_ubu_asan: *workflow_ubuntu2004_asan
|
||||
- t_ubu_asan_clang: *workflow_ubuntu2004_asan_clang
|
||||
- t_ubu_asan_cli: *workflow_ubuntu2004_asan
|
||||
|
||||
# UBSan build and tests
|
||||
|
@ -35,6 +35,21 @@
|
||||
|
||||
set -eu
|
||||
|
||||
function validate_checksum {
|
||||
local package="$1"
|
||||
local expected_checksum="$2"
|
||||
|
||||
local actual_checksum
|
||||
actual_checksum=$(sha256sum "$package")
|
||||
if [[ $actual_checksum != "${expected_checksum} ${package}" ]]
|
||||
then
|
||||
>&2 echo "ERROR: Wrong checksum for package $package."
|
||||
>&2 echo "Actual: $actual_checksum"
|
||||
>&2 echo "Expected: $expected_checksum"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
if [ ! -f /usr/local/lib/libz3.a ] # if this file does not exists (cache was not restored), rebuild dependencies
|
||||
then
|
||||
git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow
|
||||
@ -48,23 +63,31 @@ then
|
||||
./scripts/install_obsolete_jsoncpp_1_7_4.sh
|
||||
|
||||
# z3
|
||||
z3_version="z3-4.8.12"
|
||||
osx_version="osx-10.15.7"
|
||||
wget "https://github.com/Z3Prover/z3/releases/download/$z3_version/$z3_version-x64-$osx_version.zip"
|
||||
unzip "$z3_version-x64-$osx_version.zip"
|
||||
rm -f "$z3_version-x64-$osx_version.zip"
|
||||
cp "$z3_version-x64-$osx_version/bin/libz3.a" /usr/local/lib
|
||||
cp "$z3_version-x64-$osx_version/bin/z3" /usr/local/bin
|
||||
cp "$z3_version-x64-$osx_version"/include/* /usr/local/include
|
||||
rm -rf "$z3_version-x64-$osx_version"
|
||||
z3_version="4.8.12"
|
||||
z3_dir="z3-${z3_version}-x64-osx-10.15.7"
|
||||
z3_package="${z3_dir}.zip"
|
||||
wget "https://github.com/Z3Prover/z3/releases/download/z3-${z3_version}/${z3_package}"
|
||||
validate_checksum "$z3_package" a1f6ef3c99456147c4d3f2652dc6bc90951c4ab3fe7741a255eb794f0ab8938c
|
||||
unzip "$z3_package"
|
||||
rm "$z3_package"
|
||||
cp "${z3_dir}/bin/libz3.a" /usr/local/lib
|
||||
cp "${z3_dir}/bin/z3" /usr/local/bin
|
||||
cp "${z3_dir}/include/"* /usr/local/include
|
||||
rm -r "$z3_dir"
|
||||
|
||||
# evmone
|
||||
wget https://github.com/ethereum/evmone/releases/download/v0.8.0/evmone-0.8.0-darwin-x86_64.tar.gz
|
||||
tar xzpf evmone-0.8.0-darwin-x86_64.tar.gz -C /usr/local
|
||||
rm -f evmone-0.8.0-darwin-x86_64.tar.gz
|
||||
evmone_version="0.8.0"
|
||||
evmone_package="evmone-${evmone_version}-darwin-x86_64.tar.gz"
|
||||
wget "https://github.com/ethereum/evmone/releases/download/v${evmone_version}/${evmone_package}"
|
||||
validate_checksum "$evmone_package" e8efef478822f0ed6d0493e89004181e895893f93963152a2a81589acc3a0828
|
||||
tar xzpf "$evmone_package" -C /usr/local
|
||||
rm "$evmone_package"
|
||||
|
||||
# hera
|
||||
wget https://github.com/ewasm/hera/releases/download/v0.5.0/hera-0.5.0-darwin-x86_64.tar.gz
|
||||
tar xzpf hera-0.5.0-darwin-x86_64.tar.gz -C /usr/local
|
||||
rm -f hera-0.5.0-darwin-x86_64.tar.gz
|
||||
hera_version="0.5.0"
|
||||
hera_package="hera-${hera_version}-darwin-x86_64.tar.gz"
|
||||
wget "https://github.com/ewasm/hera/releases/download/v${hera_version}/${hera_package}"
|
||||
validate_checksum "$hera_package" 190050d7ace384ecd79ec1b1f607a9ff40e196b4eec75932958d4814d221d059
|
||||
tar xzpf "$hera_package" -C /usr/local
|
||||
rm "$hera_package"
|
||||
fi
|
||||
|
@ -21,7 +21,7 @@ include(EthPolicy)
|
||||
eth_policy()
|
||||
|
||||
# project name and version should be set after cmake_policy CMP0048
|
||||
set(PROJECT_VERSION "0.8.8")
|
||||
set(PROJECT_VERSION "0.8.10")
|
||||
# OSX target needed in order to support std::visit
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
|
||||
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
|
||||
@ -34,6 +34,7 @@ endif()
|
||||
|
||||
option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF)
|
||||
option(SOLC_STATIC_STDLIBS "Link solc against static versions of libgcc and libstdc++ on supported platforms" OFF)
|
||||
option(STRICT_Z3_VERSION "Use the latest version of Z3" ON)
|
||||
|
||||
# Setup cccache.
|
||||
include(EthCcache)
|
||||
@ -63,8 +64,29 @@ configure_file("${CMAKE_SOURCE_DIR}/cmake/templates/license.h.in" include/licens
|
||||
|
||||
include(EthOptions)
|
||||
configure_project(TESTS)
|
||||
set(LATEST_Z3_VERSION "4.8.12")
|
||||
set(MINIMUM_Z3_VERSION "4.8.0")
|
||||
find_package(Z3)
|
||||
if (${Z3_FOUND})
|
||||
if (${STRICT_Z3_VERSION})
|
||||
if (NOT ("${Z3_VERSION_STRING}" VERSION_EQUAL ${LATEST_Z3_VERSION}))
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"SMTChecker tests require Z3 ${LATEST_Z3_VERSION} for all tests to pass.\n\
|
||||
Build with -DSTRICT_Z3_VERSION=OFF if you want to use a different version. \
|
||||
You can also use -DUSE_Z3=OFF to build without Z3. In both cases use --no-smt when running tests."
|
||||
)
|
||||
endif()
|
||||
else()
|
||||
if ("${Z3_VERSION_STRING}" VERSION_LESS ${MINIMUM_Z3_VERSION})
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Solidity requires Z3 ${MINIMUM_Z3_VERSION} or newer. You can also use -DUSE_Z3=OFF to build without Z3."
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
find_package(Z3 4.8.0)
|
||||
if(${USE_Z3_DLOPEN})
|
||||
add_definitions(-DHAVE_Z3)
|
||||
add_definitions(-DHAVE_Z3_DLOPEN)
|
||||
|
@ -81,7 +81,7 @@ tuple<float, float> myNamespace::meanAndSigma(vector<float> const& _v)
|
||||
- Copyright
|
||||
- License (e.g. see COPYING)
|
||||
2. Never use `#ifdef`/`#define`/`#endif` file guards. Prefer `#pragma` once as first line below file comment.
|
||||
3. Prefer static const variable to value macros.
|
||||
3. Prefer static constexpr variables to value macros.
|
||||
4. Prefer inline constexpr functions to function macros.
|
||||
5. Split complex macro on multiple lines with `\`.
|
||||
|
||||
|
63
Changelog.md
63
Changelog.md
@ -6,23 +6,72 @@ Breaking changes:
|
||||
* Inline Assembly: Consider functions, function parameters and return variables for shadowing checks.
|
||||
|
||||
|
||||
### 0.8.8 (unreleased)
|
||||
### 0.8.10 (unreleased)
|
||||
|
||||
Language Features:
|
||||
* Inline Assembly: Support ``.address`` and ``.selector`` on external function pointers to access their address and function selector.
|
||||
|
||||
|
||||
Compiler Features:
|
||||
* Commandline Interface: Normalize paths specified on the command line and make them relative for files located inside base path.
|
||||
* Immutable variables can be read at construction time once they are initialized.
|
||||
* SMTChecker: Support low level ``call`` as external calls to unknown code.
|
||||
* SMTChecker: Add constraints to better correlate ``address(this).balance`` and ``msg.value``.
|
||||
* Commandline Interface: Disallowed the ``--experimental-via-ir`` option to be used with Standard Json, Assembler and Linker modes.
|
||||
* SMTChecker: Output values for ``block.*``, ``msg.*`` and ``tx.*`` variables that are present in the called functions.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
* Commandline Interface: Fix extra newline character being appended to sources passed through standard input, affecting their hashes.
|
||||
* SMTChecker: Fix internal error in magic type access (``block``, ``msg``, ``tx``).
|
||||
|
||||
|
||||
|
||||
|
||||
### 0.8.9 (2021-09-29)
|
||||
|
||||
Important Bugfixes:
|
||||
* Immutables: Properly perform sign extension on signed immutables.
|
||||
* User Defined Value Type: Fix storage layout of user defined value types for underlying types shorter than 32 bytes.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
* AST: Export ``canonicalName`` for ``UserDefinedValueTypeDefinition`` and ``ContractDefinition``.
|
||||
|
||||
|
||||
|
||||
### 0.8.8 (2021-09-27)
|
||||
|
||||
Language Features:
|
||||
* Inheritance: A function that overrides only a single interface function does not require the ``override`` specifier.
|
||||
* Type System: Support ``type(E).min`` and ``type(E).max`` for enums.
|
||||
* User Defined Value Type: allows creating a zero cost abstraction over a value type with stricter type requirements.
|
||||
|
||||
|
||||
Compiler Features:
|
||||
* Commandline Interface: Add ``--include-path`` option for specifying extra directories that may contain importable code (e.g. packaged third-party libraries).
|
||||
* Commandline Interface: Do not implicitly run evm bytecode generation unless needed for the requested output.
|
||||
* Commandline Interface: Normalize paths specified on the command line and make them relative for files located inside base path and/or include paths.
|
||||
* Immutable variables can be read at construction time once they are initialized.
|
||||
* SMTChecker: Add constraints to better correlate ``address(this).balance`` and ``msg.value``.
|
||||
* SMTChecker: Support constants via modules.
|
||||
* SMTChecker: Support low level ``call`` as external calls to unknown code.
|
||||
* SMTChecker: Support the ``value`` option for external function calls.
|
||||
* SMTChecker: Support user defined value types.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
* Code Generator: Fix ICE on assigning to calldata structs and statically-sized calldata arrays in inline assembly.
|
||||
* Code Generator: Use stable source order for ABI functions.
|
||||
* Commandline Interface: Disallow the ``--experimental-via-ir`` option in Standard JSON, Assembler and Linker modes.
|
||||
* Commandline Interface: Fix resolution of paths whitelisted with ``--allowed-paths`` or implicitly due to base path, remappings and files being compiled. Correctly handle paths that do not match imports exactly due to being relative, non-normalized or empty.
|
||||
* Commandline Interface: Report optimizer options as invalid in Standard JSON and linker modes instead of ignoring them.
|
||||
* Name Resolver: Fix that when importing an aliased symbol using ``import {AliasedName} from "a.sol"`` it would use the original name of the symbol and not the aliased one.
|
||||
* Opcode Optimizer: Prevent the optimizer from running multiple times to avoid potential bytecode differences for referenced code.
|
||||
* Parser: Properly check for multiple SPDX license identifiers next to each other and validate them.
|
||||
* SMTChecker: Fix BMC's constraints regarding internal functions.
|
||||
* SMTChecker: Fix false negative caused by ``push`` on storage array references returned by internal functions.
|
||||
* SMTChecker: Fix false positive in external calls from constructors.
|
||||
* SMTChecker: Fix internal error on some multi-source uses of ``abi.*``, cryptographic functions and constants.
|
||||
* SMTChecker: Fix false negative caused by ``push`` on storage array references returned by internal functions.
|
||||
* Standard JSON: Fix non-fatal errors in Yul mode being discarded if followed by a fatal error.
|
||||
* Type Checker: Correct wrong error message in inline assembly complaining about ``.slot`` or ``.offset` not valid when actually ``.length`` was used.
|
||||
* Type Checker: Disallow modifier declarations and definitions in interfaces.
|
||||
* Yul Optimizer: Fix a crash in LoadResolver, when ``keccak256`` has particular non-identifier arguments.
|
||||
|
||||
|
||||
|
||||
|
@ -76,7 +76,7 @@ Please follow the
|
||||
if you want to help.
|
||||
|
||||
You can find our current feature and bug priorities for forthcoming
|
||||
releases [in the projects section](https://github.com/ethereum/solidity/projects).
|
||||
releases in the [projects section](https://github.com/ethereum/solidity/projects).
|
||||
|
||||
## Maintainers
|
||||
* [@axic](https://github.com/axic)
|
||||
|
@ -102,14 +102,6 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA
|
||||
|
||||
# Some Linux-specific Clang settings. We don't want these for OS X.
|
||||
if ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux")
|
||||
|
||||
# TODO - Is this even necessary? Why?
|
||||
# See http://stackoverflow.com/questions/19774778/when-is-it-necessary-to-use-use-the-flag-stdlib-libstdc.
|
||||
add_compile_options(-stdlib=libstdc++)
|
||||
|
||||
# Tell Boost that we're using Clang's libc++. Not sure exactly why we need to do.
|
||||
add_definitions(-DBOOST_ASIO_HAS_CLANG_LIBCXX)
|
||||
|
||||
# Use fancy colors in the compiler diagnostics
|
||||
add_compile_options(-fcolor-diagnostics)
|
||||
|
||||
@ -200,6 +192,10 @@ if (SANITIZE)
|
||||
elseif (sanitizer STREQUAL "undefined")
|
||||
# The following flags not used by fuzzer but used by us may create problems, so consider
|
||||
# disabling them: alignment, pointer-overflow.
|
||||
# The following flag is not used by us to reduce terminal noise
|
||||
# i.e., warnings printed on stderr: unsigned-integer-overflow
|
||||
# Note: The C++ standard does not officially consider unsigned integer overflows
|
||||
# to be undefined behavior since they are implementation independent.
|
||||
# Flags are alphabetically sorted and are for clang v10.0
|
||||
list(APPEND undefinedSanitizerChecks
|
||||
alignment
|
||||
@ -217,18 +213,12 @@ if (SANITIZE)
|
||||
returns-nonnull-attribute
|
||||
shift
|
||||
signed-integer-overflow
|
||||
unsigned-integer-overflow
|
||||
unreachable
|
||||
vla-bound
|
||||
vptr
|
||||
)
|
||||
list(JOIN undefinedSanitizerChecks "," sanitizerChecks)
|
||||
list(REMOVE_ITEM undefinedSanitizerChecks unsigned-integer-overflow)
|
||||
# The fuzzer excludes reports of unsigned-integer-overflow. Hence, we remove it
|
||||
# from the -fno-sanitize-recover checks. Consider reducing this list if we do not
|
||||
# want to be notified about other failed checks.
|
||||
list(JOIN undefinedSanitizerChecks "," dontRecoverFromChecks)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=${sanitizerChecks} -fno-sanitize-recover=${dontRecoverFromChecks}")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=${sanitizerChecks} -fno-sanitize-recover=${sanitizerChecks}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
@ -25,13 +25,20 @@ 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)
|
||||
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")
|
||||
|
||||
find_package(Boost 1.65.0 QUIET REQUIRED COMPONENTS ${BOOST_COMPONENTS})
|
||||
if (WIN32)
|
||||
# Boost 1.77 fixes a bug that causes crashes on Windows for some relative paths in --allow-paths.
|
||||
# See https://github.com/boostorg/filesystem/issues/201
|
||||
find_package(Boost 1.77.0 QUIET REQUIRED COMPONENTS ${BOOST_COMPONENTS})
|
||||
else()
|
||||
# Boost 1.65 is the first to also provide boost::get for rvalue-references (#5787).
|
||||
find_package(Boost 1.65.0 QUIET REQUIRED COMPONENTS ${BOOST_COMPONENTS})
|
||||
endif()
|
||||
|
||||
# If cmake is older than boost and boost is older than 1.70,
|
||||
# find_package does not define imported targets, so we have to
|
||||
|
@ -113,6 +113,9 @@ them.
|
||||
+-------------------------------+-----------------------------------------------------------------------------+
|
||||
|:ref:`enum<enums>` |``uint8`` |
|
||||
+-------------------------------+-----------------------------------------------------------------------------+
|
||||
|:ref:`user defined value types |its underlying value type |
|
||||
|<user-defined-value-types>` | |
|
||||
+-------------------------------+-----------------------------------------------------------------------------+
|
||||
|:ref:`struct<structs>` |``tuple`` |
|
||||
+-------------------------------+-----------------------------------------------------------------------------+
|
||||
|
||||
|
@ -35,9 +35,9 @@ Example
|
||||
-------
|
||||
|
||||
The following example provides library code to access the code of another contract and
|
||||
load it into a ``bytes`` variable. This is not possible with "plain Solidity" and the
|
||||
idea is that reusable assembly libraries can enhance the Solidity language
|
||||
without a compiler change.
|
||||
load it into a ``bytes`` variable. This is possible with "plain Solidity" too, by using
|
||||
``<address>.code``. But the point here is that reusable assembly libraries can enhance the
|
||||
Solidity language without a compiler change.
|
||||
|
||||
.. code-block:: solidity
|
||||
|
||||
@ -124,9 +124,43 @@ Access to External Variables, Functions and Libraries
|
||||
You can access Solidity variables and other identifiers by using their name.
|
||||
|
||||
Local variables of value type are directly usable in inline assembly.
|
||||
They can both be read and assigned to.
|
||||
|
||||
Local variables that refer to memory or calldata evaluate to the
|
||||
address of the variable in memory, resp. calldata, not the value itself.
|
||||
Local variables that refer to memory evaluate to the address of the variable in memory not the value itself.
|
||||
Such variables can also be assigned to, but note that an assignment will only change the pointer and not the data
|
||||
and that it is your responsibility to respect Solidity's memory management.
|
||||
See :ref:`Conventions in Solidity <conventions-in-solidity>`.
|
||||
|
||||
Similarly, local variables that refer to statically-sized calldata arrays or calldata structs
|
||||
evaluate to the address of the variable in calldata, not the value itself.
|
||||
The variable can also be assigned a new offset, but note that no validation to ensure that
|
||||
the variable will not point beyond ``calldatasize()`` is performed.
|
||||
|
||||
For external function pointers the address and the function selector can be
|
||||
accessed using ``x.address`` and ``x.selector``.
|
||||
The selector consists of four right-aligned bytes.
|
||||
Both values are can be assigned to. For example:
|
||||
|
||||
.. code-block:: solidity
|
||||
:force:
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >=0.8.10 <0.9.0;
|
||||
|
||||
contract C {
|
||||
// Assigns a new selector and address to the return variable @fun
|
||||
function combineToFunctionPointer(address newAddress, uint newSelector) public pure returns (function() external fun) {
|
||||
assembly {
|
||||
fun.selector := newSelector
|
||||
fun.address := newAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
For dynamic calldata arrays, you can access
|
||||
their calldata offset (in bytes) and length (number of elements) using ``x.offset`` and ``x.length``.
|
||||
Both expressions can also be assigned to, but as for the static case, no validation will be performed
|
||||
to ensure that the resulting data area is within the bounds of ``calldatasize()``.
|
||||
|
||||
For local storage variables or state variables, a single Yul identifier
|
||||
is not sufficient, since they do not necessarily occupy a single full storage slot.
|
||||
@ -135,9 +169,10 @@ inside that slot. To retrieve the slot pointed to by the variable ``x``, you
|
||||
use ``x.slot``, and to retrieve the byte-offset you use ``x.offset``.
|
||||
Using ``x`` itself will result in an error.
|
||||
|
||||
For dynamic calldata arrays, you can access
|
||||
their calldata offset (in bytes) and length (number of elements) using ``x.offset`` and ``x.length``.
|
||||
Both expressions can also be assigned to.
|
||||
You can also assign to the ``.slot`` part of a local storage variable pointer.
|
||||
For these (structs, arrays or mappings), the ``.offset`` part is always zero.
|
||||
It is not possible to assign to the ``.slot`` or ``.offset`` part of a state variable,
|
||||
though.
|
||||
|
||||
Local Solidity variables are available for assignments, for example:
|
||||
|
||||
@ -178,17 +213,6 @@ Since Solidity 0.7.0, variables and functions declared inside the
|
||||
inline assembly block may not contain ``.``, but using ``.`` is
|
||||
valid to access Solidity variables from outside the inline assembly block.
|
||||
|
||||
Assignments are possible to assembly-local variables and to function-local
|
||||
variables. Take care that when you assign to variables that point to
|
||||
memory or storage, you will only change the pointer and not the data.
|
||||
|
||||
You can assign to the ``.slot`` part of a local storage variable pointer.
|
||||
For these (structs, arrays or mappings), the ``.offset`` part is always zero.
|
||||
It is not possible to assign to the ``.slot`` or ``.offset`` part of a state variable,
|
||||
though.
|
||||
|
||||
|
||||
|
||||
Things to Avoid
|
||||
---------------
|
||||
|
||||
@ -199,6 +223,8 @@ functional-style opcodes, counting stack height for
|
||||
variable access and removing stack slots for assembly-local variables when the end
|
||||
of their block is reached.
|
||||
|
||||
.. _conventions-in-solidity:
|
||||
|
||||
Conventions in Solidity
|
||||
-----------------------
|
||||
|
||||
|
@ -1,4 +1,25 @@
|
||||
[
|
||||
{
|
||||
"uid": "SOL-2021-4",
|
||||
"name": "UserDefinedValueTypesBug",
|
||||
"summary": "User defined value types with underlying type shorter than 32 bytes used incorrect storage layout and wasted storage",
|
||||
"description": "The compiler did not correctly compute the storage layout of user defined value types based on types that are shorter than 32 bytes. It would always use a full storage slot for these types, even if the underlying type was shorter. This was wasteful and might have problems with tooling or contract upgrades.",
|
||||
"link": "https://blog.soliditylang.org/2021/09/29/user-defined-value-types-bug/",
|
||||
"introduced": "0.8.8",
|
||||
"fixed": "0.8.9",
|
||||
"severity": "very low"
|
||||
|
||||
},
|
||||
{
|
||||
"uid": "SOL-2021-3",
|
||||
"name": "SignedImmutables",
|
||||
"summary": "Immutable variables of signed integer type shorter than 256 bits can lead to values with invalid higher order bits if inline assembly is used.",
|
||||
"description": "When immutable variables of signed integer type shorter than 256 bits are read, their higher order bits were unconditionally set to zero. The correct operation would be to sign-extend the value, i.e. set the higher order bits to one if the sign bit is one. This sign-extension is performed by Solidity just prior to when it matters, i.e. when a value is stored in memory, when it is compared or when a division is performed. Because of that, to our knowledge, the only way to access the value in its unclean state is by reading it through inline assembly.",
|
||||
"link": "https://blog.soliditylang.org/2021/09/29/signed-immutables-bug/",
|
||||
"introduced": "0.6.5",
|
||||
"fixed": "0.8.9",
|
||||
"severity": "very low"
|
||||
},
|
||||
{
|
||||
"uid": "SOL-2021-2",
|
||||
"name": "ABIDecodeTwoDimensionalArrayMemory",
|
||||
|
@ -1333,6 +1333,7 @@
|
||||
},
|
||||
"0.6.10": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory",
|
||||
"KeccakCaching",
|
||||
"EmptyByteArrayCopy",
|
||||
@ -1342,6 +1343,7 @@
|
||||
},
|
||||
"0.6.11": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory",
|
||||
"KeccakCaching",
|
||||
"EmptyByteArrayCopy",
|
||||
@ -1351,6 +1353,7 @@
|
||||
},
|
||||
"0.6.12": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory",
|
||||
"KeccakCaching",
|
||||
"EmptyByteArrayCopy",
|
||||
@ -1402,6 +1405,7 @@
|
||||
},
|
||||
"0.6.5": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory",
|
||||
"KeccakCaching",
|
||||
"EmptyByteArrayCopy",
|
||||
@ -1415,6 +1419,7 @@
|
||||
},
|
||||
"0.6.6": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory",
|
||||
"KeccakCaching",
|
||||
"EmptyByteArrayCopy",
|
||||
@ -1427,6 +1432,7 @@
|
||||
},
|
||||
"0.6.7": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory",
|
||||
"KeccakCaching",
|
||||
"EmptyByteArrayCopy",
|
||||
@ -1439,6 +1445,7 @@
|
||||
},
|
||||
"0.6.8": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory",
|
||||
"KeccakCaching",
|
||||
"EmptyByteArrayCopy",
|
||||
@ -1448,6 +1455,7 @@
|
||||
},
|
||||
"0.6.9": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory",
|
||||
"KeccakCaching",
|
||||
"EmptyByteArrayCopy",
|
||||
@ -1458,6 +1466,7 @@
|
||||
},
|
||||
"0.7.0": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory",
|
||||
"KeccakCaching",
|
||||
"EmptyByteArrayCopy",
|
||||
@ -1467,6 +1476,7 @@
|
||||
},
|
||||
"0.7.1": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory",
|
||||
"KeccakCaching",
|
||||
"EmptyByteArrayCopy",
|
||||
@ -1477,6 +1487,7 @@
|
||||
},
|
||||
"0.7.2": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory",
|
||||
"KeccakCaching",
|
||||
"EmptyByteArrayCopy",
|
||||
@ -1486,6 +1497,7 @@
|
||||
},
|
||||
"0.7.3": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory",
|
||||
"KeccakCaching",
|
||||
"EmptyByteArrayCopy"
|
||||
@ -1494,6 +1506,7 @@
|
||||
},
|
||||
"0.7.4": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory",
|
||||
"KeccakCaching"
|
||||
],
|
||||
@ -1501,6 +1514,7 @@
|
||||
},
|
||||
"0.7.5": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory",
|
||||
"KeccakCaching"
|
||||
],
|
||||
@ -1508,6 +1522,7 @@
|
||||
},
|
||||
"0.7.6": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory",
|
||||
"KeccakCaching"
|
||||
],
|
||||
@ -1515,6 +1530,7 @@
|
||||
},
|
||||
"0.8.0": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory",
|
||||
"KeccakCaching"
|
||||
],
|
||||
@ -1522,6 +1538,7 @@
|
||||
},
|
||||
"0.8.1": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory",
|
||||
"KeccakCaching"
|
||||
],
|
||||
@ -1529,6 +1546,7 @@
|
||||
},
|
||||
"0.8.2": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory",
|
||||
"KeccakCaching"
|
||||
],
|
||||
@ -1536,24 +1554,44 @@
|
||||
},
|
||||
"0.8.3": {
|
||||
"bugs": [
|
||||
"SignedImmutables",
|
||||
"ABIDecodeTwoDimensionalArrayMemory"
|
||||
],
|
||||
"released": "2021-03-23"
|
||||
},
|
||||
"0.8.4": {
|
||||
"bugs": [],
|
||||
"bugs": [
|
||||
"SignedImmutables"
|
||||
],
|
||||
"released": "2021-04-21"
|
||||
},
|
||||
"0.8.5": {
|
||||
"bugs": [],
|
||||
"bugs": [
|
||||
"SignedImmutables"
|
||||
],
|
||||
"released": "2021-06-10"
|
||||
},
|
||||
"0.8.6": {
|
||||
"bugs": [],
|
||||
"bugs": [
|
||||
"SignedImmutables"
|
||||
],
|
||||
"released": "2021-06-22"
|
||||
},
|
||||
"0.8.7": {
|
||||
"bugs": [],
|
||||
"bugs": [
|
||||
"SignedImmutables"
|
||||
],
|
||||
"released": "2021-08-11"
|
||||
},
|
||||
"0.8.8": {
|
||||
"bugs": [
|
||||
"UserDefinedValueTypesBug",
|
||||
"SignedImmutables"
|
||||
],
|
||||
"released": "2021-09-27"
|
||||
},
|
||||
"0.8.9": {
|
||||
"bugs": [],
|
||||
"released": "2021-09-29"
|
||||
}
|
||||
}
|
@ -155,12 +155,17 @@ When a function has multiple return types, the statement ``return (v0, v1, ...,
|
||||
The number of components must be the same as the number of return variables
|
||||
and their types have to match, potentially after an :ref:`implicit conversion <types-conversion-elementary-types>`.
|
||||
|
||||
.. _state-mutability:
|
||||
|
||||
State Mutability
|
||||
================
|
||||
|
||||
.. index:: ! view function, function;view
|
||||
|
||||
.. _view-functions:
|
||||
|
||||
View Functions
|
||||
==============
|
||||
--------------
|
||||
|
||||
Functions can be declared ``view`` in which case they promise not to modify the state.
|
||||
|
||||
@ -214,7 +219,7 @@ The following statements are considered modifying the state:
|
||||
.. _pure-functions:
|
||||
|
||||
Pure Functions
|
||||
==============
|
||||
--------------
|
||||
|
||||
Functions can be declared ``pure`` in which case they promise not to read from or modify the state.
|
||||
|
||||
@ -270,12 +275,17 @@ This behaviour is also in line with the ``STATICCALL`` opcode.
|
||||
not do state-changing operations, but it cannot check that the contract that will be called
|
||||
at runtime is actually of that type.
|
||||
|
||||
.. _special-functions:
|
||||
|
||||
Special Functions
|
||||
=================
|
||||
|
||||
.. index:: ! receive ether function, function;receive ! receive
|
||||
|
||||
.. _receive-ether-function:
|
||||
|
||||
Receive Ether Function
|
||||
======================
|
||||
----------------------
|
||||
|
||||
A contract can have at most one ``receive`` function, declared using
|
||||
``receive() external payable { ... }``
|
||||
@ -346,7 +356,7 @@ Below you can see an example of a Sink contract that uses function ``receive``.
|
||||
.. _fallback-function:
|
||||
|
||||
Fallback Function
|
||||
=================
|
||||
-----------------
|
||||
|
||||
A contract can have at most one ``fallback`` function, declared using either ``fallback () external [payable]``
|
||||
or ``fallback (bytes calldata _input) external [payable] returns (bytes memory _output)``
|
||||
|
@ -303,6 +303,13 @@ contracts can no longer change the behaviour of that function.
|
||||
outside of interfaces. In interfaces, all functions are
|
||||
automatically considered ``virtual``.
|
||||
|
||||
.. note::
|
||||
|
||||
Starting from Solidity 0.8.8, the ``override`` keyword is not
|
||||
required when overriding an interface function, except for the
|
||||
case where the function is defined in multiple bases.
|
||||
|
||||
|
||||
Public state variables can override external functions if the
|
||||
parameter and return types of the function matches the getter function
|
||||
of the variable:
|
||||
|
@ -6,12 +6,14 @@
|
||||
Interfaces
|
||||
**********
|
||||
|
||||
Interfaces are similar to abstract contracts, but they cannot have any functions implemented. There are further restrictions:
|
||||
Interfaces are similar to abstract contracts, but they cannot have any functions implemented.
|
||||
There are further restrictions:
|
||||
|
||||
- They cannot inherit from other contracts, but they can inherit from other interfaces.
|
||||
- All declared functions must be external.
|
||||
- They cannot declare a constructor.
|
||||
- They cannot declare state variables.
|
||||
- They cannot declare modifiers.
|
||||
|
||||
Some of these restrictions might be lifted in the future.
|
||||
|
||||
@ -33,10 +35,10 @@ Interfaces are denoted by their own keyword:
|
||||
|
||||
Contracts can inherit interfaces as they would inherit other contracts.
|
||||
|
||||
All functions declared in interfaces are implicitly ``virtual``, which means that
|
||||
they can be overridden. This does not automatically mean that an overriding function
|
||||
can be overridden again - this is only possible if the overriding
|
||||
function is marked ``virtual``.
|
||||
All functions declared in interfaces are implicitly ``virtual`` and any
|
||||
functions that override them do not need the ``override`` keyword.
|
||||
This does not automatically mean that an overriding function can be overridden again -
|
||||
this is only possible if the overriding function is marked ``virtual``.
|
||||
|
||||
Interfaces can inherit from other interfaces. This has the same rules as normal
|
||||
inheritance.
|
||||
|
@ -100,12 +100,13 @@ Running the Tests
|
||||
-----------------
|
||||
|
||||
Solidity includes different types of tests, most of them bundled into the
|
||||
`Boost C++ Test Framework <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/index.html>`_ application ``soltest``.
|
||||
`Boost C++ Test Framework <https://www.boost.org/doc/libs/release/libs/test/doc/html/index.html>`_ application ``soltest``.
|
||||
Running ``build/test/soltest`` or its wrapper ``scripts/soltest.sh`` is sufficient for most changes.
|
||||
|
||||
The ``./scripts/tests.sh`` script executes most Solidity tests automatically,
|
||||
including those bundled into the `Boost C++ Test Framework <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/index.html>`_ application ``soltest`` (or its wrapper ``scripts/soltest.sh``),
|
||||
as well as command line tests and compilation tests.
|
||||
including those bundled into the `Boost C++ Test Framework <https://www.boost.org/doc/libs/release/libs/test/doc/html/index.html>`_
|
||||
application ``soltest`` (or its wrapper ``scripts/soltest.sh``), as well as command line tests and
|
||||
compilation tests.
|
||||
|
||||
The test system automatically tries to discover the location of the ``evmone`` library
|
||||
starting from the current directory. The required file is called ``libevmone.so`` on Linux systems,
|
||||
@ -137,9 +138,9 @@ Or, for example, to run all the tests for the yul disambiguator:
|
||||
|
||||
See especially:
|
||||
|
||||
- `show_progress (-p) <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/show_progress.html>`_ to show test completion,
|
||||
- `run_test (-t) <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/run_test.html>`_ to run specific tests cases, and
|
||||
- `report-level (-r) <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/report_level.html>`_ give a more detailed report.
|
||||
- `show_progress (-p) <https://www.boost.org/doc/libs/release/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/show_progress.html>`_ to show test completion,
|
||||
- `run_test (-t) <https://www.boost.org/doc/libs/release/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/run_test.html>`_ to run specific tests cases, and
|
||||
- `report-level (-r) <https://www.boost.org/doc/libs/release/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/report_level.html>`_ give a more detailed report.
|
||||
|
||||
.. note ::
|
||||
|
||||
|
@ -65,9 +65,10 @@ uses up at least one stack slot and there are only 1024 slots available.
|
||||
External Function Calls
|
||||
-----------------------
|
||||
|
||||
The expressions ``this.g(8);`` and ``c.g(2);`` (where ``c`` is a contract
|
||||
instance) are also valid function calls, but this time, the function
|
||||
will be called "externally", via a message call and not directly via jumps.
|
||||
Functions can also be called using the ``this.g(8);`` and ``c.g(2);`` notation, where
|
||||
``c`` is a contract instance and ``g`` is a function belonging to ``c``.
|
||||
Calling the function ``g`` via either way results in it being called "externally", using a
|
||||
message call and not directly via jumps.
|
||||
Please note that function calls on ``this`` cannot be used in the constructor,
|
||||
as the actual contract has not been created yet.
|
||||
|
||||
@ -105,8 +106,10 @@ otherwise, the ``value`` option would not be available.
|
||||
.. warning::
|
||||
Be careful that ``feed.info{value: 10, gas: 800}`` only locally sets the
|
||||
``value`` and amount of ``gas`` sent with the function call, and the
|
||||
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.
|
||||
parentheses at the end perform the actual call. So
|
||||
``feed.info{value: 10, gas: 800}`` does not call the function and
|
||||
the ``value`` and ``gas`` settings are lost, only
|
||||
``feed.info{value: 10, gas: 800}()`` performs the function call.
|
||||
|
||||
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
|
||||
|
@ -19,6 +19,7 @@ sourceUnit: (
|
||||
| constantVariableDeclaration
|
||||
| structDefinition
|
||||
| enumDefinition
|
||||
| userDefinedValueTypeDefinition
|
||||
| errorDefinition
|
||||
)* EOF;
|
||||
|
||||
@ -89,6 +90,7 @@ contractBodyElement:
|
||||
| receiveFunctionDefinition
|
||||
| structDefinition
|
||||
| enumDefinition
|
||||
| userDefinedValueTypeDefinition
|
||||
| stateVariableDeclaration
|
||||
| eventDefinition
|
||||
| errorDefinition
|
||||
@ -247,6 +249,11 @@ structMember: type=typeName name=identifier Semicolon;
|
||||
* Definition of an enum. Can occur at top-level within a source unit or within a contract, library or interface.
|
||||
*/
|
||||
enumDefinition: Enum name=identifier LBrace enumValues+=identifier (Comma enumValues+=identifier)* RBrace;
|
||||
/**
|
||||
* Definition of a user defined value type. Can occur at top-level within a source unit or within a contract, library or interface.
|
||||
*/
|
||||
userDefinedValueTypeDefinition:
|
||||
Type name=identifier Is elementaryTypeName[true] Semicolon;
|
||||
|
||||
/**
|
||||
* The declaration of a state variable.
|
||||
|
@ -313,7 +313,8 @@ The following are dependencies for all builds of Solidity:
|
||||
+===================================+=======================================================+
|
||||
| `CMake`_ (version 3.13+) | Cross-platform build file generator. |
|
||||
+-----------------------------------+-------------------------------------------------------+
|
||||
| `Boost`_ (version 1.65+) | C++ libraries. |
|
||||
| `Boost`_ (version 1.77+ on | C++ libraries. |
|
||||
| Windows, 1.65+ otherwise) | |
|
||||
+-----------------------------------+-------------------------------------------------------+
|
||||
| `Git`_ | Command-line tool for retrieving source code. |
|
||||
+-----------------------------------+-------------------------------------------------------+
|
||||
@ -335,6 +336,16 @@ The following are dependencies for all builds of Solidity:
|
||||
|
||||
Starting from 0.5.10 linking against Boost 1.70+ should work without manual intervention.
|
||||
|
||||
.. note::
|
||||
The default build configuration requires a specific Z3 version (the latest one at the time the
|
||||
code was last updated). Changes introduced between Z3 releases often result in slightly different
|
||||
(but still valid) results being returned. Our SMT tests do not account for these differences and
|
||||
will likely fail with a different version than the one they were written for. This does not mean
|
||||
that a build using a different version is faulty. If you pass ``-DSTRICT_Z3_VERSION=OFF`` option
|
||||
to CMake, you can build with any version that satisfies the requirement given in the table above.
|
||||
If you do this, however, please remember to pass the ``--no-smt`` option to ``scripts/tests.sh``
|
||||
to skip the SMT tests.
|
||||
|
||||
Minimum Compiler Versions
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -378,6 +389,8 @@ You need to install the following dependencies for Windows builds of Solidity:
|
||||
+-----------------------------------+-------------------------------------------------------+
|
||||
| `Visual Studio 2019`_ (Optional) | C++ compiler and dev environment. |
|
||||
+-----------------------------------+-------------------------------------------------------+
|
||||
| `Boost`_ (version 1.77+) | C++ libraries. |
|
||||
+-----------------------------------+-------------------------------------------------------+
|
||||
|
||||
If you already have one IDE and only need the compiler and libraries,
|
||||
you could install Visual Studio 2019 Build Tools.
|
||||
|
@ -72,8 +72,9 @@ The initial content of the VFS depends on how you invoke the compiler:
|
||||
solc contract.sol /usr/local/dapp-bin/token.sol
|
||||
|
||||
The source unit name of a file loaded this way is constructed by converting its path to a
|
||||
canonical form and making it relative to the base path if it is located inside.
|
||||
See :ref:`Base Path Normalization and Stripping <base-path-normalization-and-stripping>` for
|
||||
canonical form and, if possible, making it relative to either the base path or one of the
|
||||
include paths.
|
||||
See :ref:`CLI Path Normalization and Stripping <cli-path-normalization-and-stripping>` for
|
||||
a detailed description of this process.
|
||||
|
||||
.. index:: standard JSON
|
||||
@ -295,16 +296,36 @@ Here are some examples of what you can expect if they are not:
|
||||
|
||||
The use of relative imports containing leading ``..`` segments is not recommended.
|
||||
The same effect can be achieved in a more reliable way by using direct imports with
|
||||
:ref:`base path <base-path>` and :ref:`import remapping <import-remapping>`.
|
||||
:ref:`base path and include paths <base-and-include-paths>`.
|
||||
|
||||
.. index:: ! base path, --base-path
|
||||
.. _base-path:
|
||||
.. index:: ! base path, ! --base-path, ! include paths, ! --include-path
|
||||
.. _base-and-include-paths:
|
||||
|
||||
Base Path
|
||||
=========
|
||||
Base Path and Include Paths
|
||||
===========================
|
||||
|
||||
The base path specifies the directory that the Host Filesystem Loader will load files from.
|
||||
It is simply prepended to a source unit name before the filesystem lookup is performed.
|
||||
The base path and include paths represent directories that the Host Filesystem Loader will load files from.
|
||||
When a source unit name is passed to the loader, it prepends the base path to it and performs a
|
||||
filesystem lookup.
|
||||
If the lookup does not succeed, the same is done with all directories on the include path list.
|
||||
|
||||
It is recommended to set the base path to the root directory of your project and use include paths to
|
||||
specify additional locations that may contain libraries your project depends on.
|
||||
This lets you import from these libraries in a uniform way, no matter where they are located in the
|
||||
filesystem relative to your project.
|
||||
For example, if you use npm to install packages and your contract imports
|
||||
``@openzeppelin/contracts/utils/Strings.sol``, you can use these options to tell the compiler that
|
||||
the library can be found in one of the npm package directories:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
solc contract.sol \
|
||||
--base-path . \
|
||||
--include-path node_modules/ \
|
||||
--include-path /usr/local/lib/node_modules/
|
||||
|
||||
Your contract will compile (with the same exact metadata) no matter whether you install the library
|
||||
in the local or global package directory or even directly under your project root.
|
||||
|
||||
By default the base path is empty, which leaves the source unit name unchanged.
|
||||
When the source unit name is a relative path, this results in the file being looked up in the
|
||||
@ -314,10 +335,23 @@ interpreted as absolute paths on disk.
|
||||
If the base path itself is relative, it is interpreted as relative to the current working directory
|
||||
of the compiler.
|
||||
|
||||
.. _base-path-normalization-and-stripping:
|
||||
.. note::
|
||||
|
||||
Base Path Normalization and Stripping
|
||||
-------------------------------------
|
||||
Include paths cannot have empty values and must be used together with a non-empty base path.
|
||||
|
||||
.. note::
|
||||
|
||||
Include paths and base path can overlap as long as it does not make import resolution ambiguous.
|
||||
For example, you can specify a directory inside base path as an include directory or have an
|
||||
include directory that is a subdirectory of another include directory.
|
||||
The compiler will only issue an error if the source unit name passed to the Host Filesystem
|
||||
Loader represents an existing path when combined with multiple include paths or an include path
|
||||
and base path.
|
||||
|
||||
.. _cli-path-normalization-and-stripping:
|
||||
|
||||
CLI Path Normalization and Stripping
|
||||
------------------------------------
|
||||
|
||||
On the command line the compiler behaves just as you would expect from any other program:
|
||||
it accepts paths in a format native to the platform and relative paths are relative to the current
|
||||
@ -326,7 +360,7 @@ The source unit names assigned to files whose paths are specified on the command
|
||||
should not change just because the project is being compiled on a different platform or because the
|
||||
compiler happens to have been invoked from a different directory.
|
||||
To achieve this, paths to source files coming from the command line must be converted to a canonical
|
||||
form, and, if possible, made relative to the base path.
|
||||
form, and, if possible, made relative to the base path or one of the include paths.
|
||||
|
||||
The normalization rules are as follows:
|
||||
|
||||
@ -357,7 +391,8 @@ The normalization rules are as follows:
|
||||
You can avoid such situations by ensuring that all the files are available within a single
|
||||
directory tree on the same drive.
|
||||
|
||||
Once canonicalized, the base path is stripped from all source file paths that start with it.
|
||||
After normalization the compiler attempts to make the source file path relative.
|
||||
It tries the base path first and then the include paths in the order they were given.
|
||||
If the base path is empty or not specified, it is treated as if it was equal to the path to the
|
||||
current working directory (with all symbolic links resolved).
|
||||
The result is accepted only if the normalized directory path is the exact prefix of the normalized
|
||||
@ -366,6 +401,16 @@ Otherwise the file path remains absolute.
|
||||
This makes the conversion unambiguous and ensures that the relative path does not start with ``../``.
|
||||
The resulting file path becomes the source unit name.
|
||||
|
||||
.. note::
|
||||
|
||||
The relative path produced by stripping must remain unique within the base path and include paths.
|
||||
For example the compiler will issue an error for the following command if both
|
||||
``/project/contract.sol`` and ``/lib/contract.sol`` exist:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
solc /project/contract.sol --base-path /project --include-path /lib
|
||||
|
||||
.. note::
|
||||
|
||||
Prior to version 0.8.8, CLI path stripping was not performed and the only normalization applied
|
||||
@ -373,6 +418,74 @@ The resulting file path becomes the source unit name.
|
||||
When working with older versions of the compiler it is recommended to invoke the compiler from
|
||||
the base path and to only use relative paths on the command line.
|
||||
|
||||
.. index:: ! allowed paths, ! --allow-paths, remapping; target
|
||||
.. _allowed-paths:
|
||||
|
||||
Allowed Paths
|
||||
=============
|
||||
|
||||
As a security measure, the Host Filesystem Loader will refuse to load files from outside of a few
|
||||
locations that are considered safe by default:
|
||||
|
||||
- Outside of Standard JSON mode:
|
||||
|
||||
- The directories containing input files listed on the command line.
|
||||
- The directories used as :ref:`remapping <import-remapping>` targets.
|
||||
If the target is not a directory (i.e does not end with ``/``, ``/.`` or ``/..``) the directory
|
||||
containing the target is used instead.
|
||||
- Base path and include paths.
|
||||
|
||||
- In Standard JSON mode:
|
||||
|
||||
- Base path and include paths.
|
||||
|
||||
Additional directories can be whitelisted using the ``--allow-paths`` option.
|
||||
The option accepts a comma-separated list of paths:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
cd /home/user/project/
|
||||
solc token/contract.sol \
|
||||
lib/util.sol=libs/util.sol \
|
||||
--base-path=token/ \
|
||||
--include-path=/lib/ \
|
||||
--allow-paths=../utils/,/tmp/libraries
|
||||
|
||||
When the compiler is invoked with the command shown above, the Host Filesystem Loader will allow
|
||||
importing files from the following directories:
|
||||
|
||||
- ``/home/user/project/token/`` (because ``token/`` contains the input file and also because it is
|
||||
the base path),
|
||||
- ``/lib/`` (because ``/lib/`` is one of the include paths),
|
||||
- ``/home/user/project/libs/`` (because ``libs/`` is a directory containing a remapping target),
|
||||
- ``/home/user/utils/`` (because of ``../utils/`` passed to ``--allow-paths``),
|
||||
- ``/tmp/libraries/`` (because of ``/tmp/libraries`` passed to ``--allow-paths``),
|
||||
|
||||
.. note::
|
||||
|
||||
The working directory of the compiler is one of the paths allowed by default only if it
|
||||
happens to be the base path (or the base path is not specified or has an empty value).
|
||||
|
||||
.. note::
|
||||
|
||||
The compiler does not check if allowed paths actually exist and whether they are directories.
|
||||
Non-existent or empty paths are simply ignored.
|
||||
If an allowed path matches a file rather than a directory, the file is considered whitelisted, too.
|
||||
|
||||
.. note::
|
||||
|
||||
Allowed paths are case-sensitive even if the filesystem is not.
|
||||
The case must exactly match the one used in your imports.
|
||||
For example ``--allow-paths tokens`` will not match ``import "Tokens/IERC20.sol"``.
|
||||
|
||||
.. warning::
|
||||
|
||||
Files and directories only reachable through symbolic links from allowed directories are not
|
||||
automatically whitelisted.
|
||||
For example if ``token/contract.sol`` in the example above was actually a symlink pointing at
|
||||
``/etc/passwd`` the compiler would refuse to load it unless ``/etc/`` was one of the allowed
|
||||
paths too.
|
||||
|
||||
.. index:: ! remapping; import, ! import; remapping, ! remapping; context, ! remapping; prefix, ! remapping; target
|
||||
.. _import-remapping:
|
||||
|
||||
@ -426,6 +539,13 @@ Loader, which will then look in ``/project/dapp-bin/library/iterable_mapping.sol
|
||||
would need to recreate parts of your local directory structure in the VFS and (if you rely on
|
||||
Host Filesystem Loader) also in the host filesystem.
|
||||
|
||||
To avoid having your local directory structure embedded in the metadata, it is recommended to
|
||||
designate the directories containing libraries as *include paths* instead.
|
||||
For example, in the example above ``--include-path /home/user/packages/`` would let you use
|
||||
imports starting with ``mymath/``.
|
||||
Unlike remapping, the option on its own will not make ``mymath`` appear as ``@math`` but this
|
||||
can be achieved by creating a symbolic link or renaming the package subdirectory.
|
||||
|
||||
As a more complex example, suppose you rely on a module that uses an old version of dapp-bin that
|
||||
you checked out to ``/project/dapp-bin_old``, then you can run:
|
||||
|
||||
|
@ -353,6 +353,10 @@ code.
|
||||
Always use the latest version of the compiler to be notified about all recently
|
||||
introduced warnings.
|
||||
|
||||
Messages of type ``info`` issued by the compiler are not dangerous, and simply
|
||||
represent extra suggestions and optional information that the compiler thinks
|
||||
might be useful to the user.
|
||||
|
||||
Restrict the Amount of Ether
|
||||
============================
|
||||
|
||||
|
@ -7,7 +7,7 @@ If ``a`` is an LValue (i.e. a variable or something that can be assigned to), th
|
||||
following operators are available as shorthands:
|
||||
|
||||
``a += e`` is equivalent to ``a = a + e``. The operators ``-=``, ``*=``, ``/=``, ``%=``,
|
||||
``|=``, ``&=`` and ``^=`` are defined accordingly. ``a++`` and ``a--`` are equivalent
|
||||
``|=``, ``&=``, ``^=``, ``<<=`` and ``>>=`` are defined accordingly. ``a++`` and ``a--`` are equivalent
|
||||
to ``a += 1`` / ``a -= 1`` but the expression itself still has the previous value
|
||||
of ``a``. In contrast, ``--a`` and ``++a`` have the same effect on ``a`` but
|
||||
return the value after the change.
|
||||
|
@ -66,7 +66,7 @@ Shifts
|
||||
^^^^^^
|
||||
|
||||
The result of a shift operation has the type of the left operand, truncating the result to match the type.
|
||||
The right operand must be of unsigned type, trying to shift by an signed type will produce a compilation error.
|
||||
The right operand must be of unsigned type, trying to shift by a signed type will produce a compilation error.
|
||||
|
||||
Shifts can be "simulated" using multiplication by powers of two in the following way. Note that the truncation
|
||||
to the type of the left operand is always performed at the end, but not mentioned explicitly.
|
||||
@ -587,11 +587,14 @@ Enums cannot have more than 256 members.
|
||||
The data representation is the same as for enums in C: The options are represented by
|
||||
subsequent unsigned integer values starting from ``0``.
|
||||
|
||||
Using ``type(NameOfEnum).min`` and ``type(NameOfEnum).max`` you can get the
|
||||
smallest and respectively largest value of the given enum.
|
||||
|
||||
|
||||
.. code-block:: solidity
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >=0.4.16 <0.9.0;
|
||||
pragma solidity ^0.8.8;
|
||||
|
||||
contract test {
|
||||
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
|
||||
@ -612,11 +615,83 @@ subsequent unsigned integer values starting from ``0``.
|
||||
function getDefaultChoice() public pure returns (uint) {
|
||||
return uint(defaultChoice);
|
||||
}
|
||||
|
||||
function getLargestValue() public pure returns (ActionChoices) {
|
||||
return type(ActionChoices).max;
|
||||
}
|
||||
|
||||
function getSmallestValue() public pure returns (ActionChoices) {
|
||||
return type(ActionChoices).min;
|
||||
}
|
||||
}
|
||||
|
||||
.. note::
|
||||
Enums can also be declared on the file level, outside of contract or library definitions.
|
||||
|
||||
.. index:: ! user defined value type, custom type
|
||||
|
||||
.. _user-defined-value-types:
|
||||
|
||||
User Defined Value Types
|
||||
------------------------
|
||||
|
||||
A user defined value type allows creating a zero cost abstraction over an elementary value type.
|
||||
This is similar to an alias, but with stricter type requirements.
|
||||
|
||||
A user defined value type is defined using ``type C is V``, where ``C`` is the name of the newly
|
||||
introduced type and ``V`` has to be a built-in value type (the "underlying type"). The function
|
||||
``C.wrap`` is used to convert from the underlying type to the custom type. Similarly, the
|
||||
function ``C.unwrap`` is used to convert from the custom type to the underlying type.
|
||||
|
||||
The type ``C`` does not have any operators or bound member functions. In particular, even the
|
||||
operator ``==`` is not defined. Explicit and implicit conversions to and from other types are
|
||||
disallowed.
|
||||
|
||||
The data-representation of values of such types are inherited from the underlying type
|
||||
and the underlying type is also used in the ABI.
|
||||
|
||||
The following example illustrates a custom type ``UFixed256x18`` representing a decimal fixed point
|
||||
type with 18 decimals and a minimal library to do arithmetic operations on the type.
|
||||
|
||||
|
||||
.. code-block:: solidity
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity ^0.8.8;
|
||||
|
||||
// Represent a 18 decimal, 256 bit wide fixed point type using a user defined value type.
|
||||
type UFixed256x18 is uint256;
|
||||
|
||||
/// A minimal library to do fixed point operations on UFixed256x18.
|
||||
library FixedMath {
|
||||
uint constant multiplier = 10**18;
|
||||
|
||||
/// Adds two UFixed256x18 numbers. Reverts on overflow, relying on checked
|
||||
/// arithmetic on uint256.
|
||||
function add(UFixed256x18 a, UFixed256x18 b) internal pure returns (UFixed256x18) {
|
||||
return UFixed256x18.wrap(UFixed256x18.unwrap(a) + UFixed256x18.unwrap(b));
|
||||
}
|
||||
/// Multiplies UFixed256x18 and uint256. Reverts on overflow, relying on checked
|
||||
/// arithmetic on uint256.
|
||||
function mul(UFixed256x18 a, uint256 b) internal pure returns (UFixed256x18) {
|
||||
return UFixed256x18.wrap(UFixed256x18.unwrap(a) * b);
|
||||
}
|
||||
/// Take the floor of a UFixed256x18 number.
|
||||
/// @return the largest integer that does not exceed `a`.
|
||||
function floor(UFixed256x18 a) internal pure returns (uint256) {
|
||||
return UFixed256x18.unwrap(a) / multiplier;
|
||||
}
|
||||
/// Turns a uint256 into a UFixed256x18 of the same value.
|
||||
/// Reverts if the integer is too large.
|
||||
function toUFixed256x18(uint256 a) internal pure returns (UFixed256x18) {
|
||||
return UFixed256x18.wrap(a * multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
Notice how ``UFixed256x18.wrap`` and ``FixedMath.toUFixed256x18`` have the same signature but
|
||||
perform two very different operations: The ``UFixed256x18.wrap`` function returns a ``UFixed256x18``
|
||||
that has the same data representation as the input, whereas ``toUFixed256x18`` returns a
|
||||
``UFixed256x18`` that has the same numerical value.
|
||||
|
||||
.. index:: ! function type, ! type; function
|
||||
|
||||
|
@ -33,7 +33,7 @@ This parameter has effects on the following (this might change in the future):
|
||||
- the size of the binary search in the function dispatch routine
|
||||
- the way constants like large numbers or strings are stored
|
||||
|
||||
.. index:: allowed paths, --allow-paths, base path, --base-path
|
||||
.. index:: allowed paths, --allow-paths, base path, --base-path, include paths, --include-path
|
||||
|
||||
Base Path and Import Remapping
|
||||
------------------------------
|
||||
@ -47,16 +47,13 @@ it is also possible to provide :ref:`path redirects <import-remapping>` using ``
|
||||
|
||||
This essentially instructs the compiler to search for anything starting with
|
||||
``github.com/ethereum/dapp-bin/`` under ``/usr/local/lib/dapp-bin``.
|
||||
``solc`` will not read files from the filesystem that lie outside of
|
||||
the remapping targets and outside of the directories where explicitly specified source
|
||||
files reside, so things like ``import "/etc/passwd";`` only work if you add ``/=/`` as a remapping.
|
||||
|
||||
When accessing the filesystem to search for imports, :ref:`paths that do not start with ./
|
||||
or ../ <relative-imports>` are treated as relative to the directory specified using
|
||||
``--base-path`` option (or the current working directory if base path is not specified).
|
||||
Furthermore, the part added via ``--base-path`` will not appear in the contract metadata.
|
||||
or ../ <direct-imports>` are treated as relative to the directories specified using
|
||||
``--base-path`` and ``--include-path`` options (or the current working directory if base path is not specified).
|
||||
Furthermore, the part of the path added via these options will not appear in the contract metadata.
|
||||
|
||||
For security reasons the compiler has restrictions on what directories it can access.
|
||||
For security reasons the compiler has :ref:`restrictions on what directories it can access <allowed-paths>`.
|
||||
Directories of source files specified on the command line and target paths of
|
||||
remappings are automatically allowed to be accessed by the file reader, but everything
|
||||
else is rejected by default.
|
||||
@ -437,7 +434,7 @@ Output Description
|
||||
.. code-block:: javascript
|
||||
|
||||
{
|
||||
// Optional: not present if no errors/warnings were encountered
|
||||
// Optional: not present if no errors/warnings/infos were encountered
|
||||
"errors": [
|
||||
{
|
||||
// Optional: Location within the source file.
|
||||
@ -460,7 +457,7 @@ Output Description
|
||||
"type": "TypeError",
|
||||
// Mandatory: Component where the error originated, such as "general", "ewasm", etc.
|
||||
"component": "general",
|
||||
// Mandatory ("error" or "warning")
|
||||
// Mandatory ("error", "warning" or "info", but please note that this may be extended in the future)
|
||||
"severity": "error",
|
||||
// Optional: unique code for the cause of the error
|
||||
"errorCode": "3141",
|
||||
@ -604,6 +601,7 @@ Error Types
|
||||
11. ``CompilerError``: Invalid use of the compiler stack - this should be reported as an issue.
|
||||
12. ``FatalError``: Fatal error not processed correctly - this should be reported as an issue.
|
||||
13. ``Warning``: A warning, which didn't stop the compilation, but should be addressed if possible.
|
||||
14. ``Info``: Information that the compiler thinks the user might find useful, but is not dangerous and does not necessarily need to be addressed.
|
||||
|
||||
|
||||
.. _compiler-tools:
|
||||
|
32
docs/yul.rst
32
docs/yul.rst
@ -174,9 +174,32 @@ whitespace, i.e. there is no terminating ``;`` or newline required.
|
||||
Literals
|
||||
--------
|
||||
|
||||
As literals, you can use integer constants in decimal or hexadecimal notation
|
||||
or strings as ASCII (`"abc"`) or HEX strings (`hex"616263"`) of up to
|
||||
32 bytes length.
|
||||
As literals, you can use:
|
||||
|
||||
- Integer constants in decimal or hexadecimal notation.
|
||||
|
||||
- ASCII strings (e.g. ``"abc"``), which may contain hex escapes ``\xNN`` and Unicode escapes ``\uNNNN`` where ``N`` are hexadecimal digits.
|
||||
|
||||
- Hex strings (e.g. ``hex"616263"``).
|
||||
|
||||
In the EVM dialect of Yul, literals represent 256-bit words as follows:
|
||||
|
||||
- Decimal or hexadecimal constants must be less than ``2**256``.
|
||||
They represent the 256-bit word with that value as an unsigned integer in big endian encoding.
|
||||
|
||||
- An ASCII string is first viewed as a byte sequence, by viewing
|
||||
a non-escape ASCII character as a single byte whose value is the ASCII code,
|
||||
an escape ``\xNN`` as single byte with that value, and
|
||||
an escape ``\uNNNN`` as the UTF-8 sequence of bytes for that code point.
|
||||
The byte sequence must not exceed 32 bytes.
|
||||
The byte sequence is padded with zeros on the right to reach 32 bytes in length;
|
||||
in other words, the string is stored left-aligned.
|
||||
The padded byte sequence represents a 256-bit word whose most significant 8 bits are the ones from the first byte,
|
||||
i.e. the bytes are interpreted in big endian form.
|
||||
|
||||
- A hex string is first viewed as a byte sequence, by viewing
|
||||
each pair of contiguous hex digits as a byte.
|
||||
The byte sequence must not exceed 32 bytes (i.e. 64 hex digits), and is treated as above.
|
||||
|
||||
When compiling for the EVM, this will be translated into an
|
||||
appropriate ``PUSHi`` instruction. In the following example,
|
||||
@ -184,8 +207,7 @@ appropriate ``PUSHi`` instruction. In the following example,
|
||||
bitwise ``and`` with the string "abc" is computed.
|
||||
The final value is assigned to a local variable called ``x``.
|
||||
|
||||
Strings are stored left-aligned and cannot be longer than 32 bytes.
|
||||
The limit does not apply to string literals passed to builtin functions that require
|
||||
The 32-byte limit above does not apply to string literals passed to builtin functions that require
|
||||
literal arguments (e.g. ``setimmutable`` or ``loadimmutable``). Those strings never end up in the
|
||||
generated bytecode.
|
||||
|
||||
|
@ -31,12 +31,16 @@
|
||||
#include <libevmasm/ConstantOptimiser.h>
|
||||
#include <libevmasm/GasMeter.h>
|
||||
|
||||
#include <liblangutil/CharStream.h>
|
||||
#include <liblangutil/Exceptions.h>
|
||||
|
||||
#include <json/json.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <range/v3/algorithm/any_of.hpp>
|
||||
#include <range/v3/view/enumerate.hpp>
|
||||
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
@ -55,7 +59,7 @@ AssemblyItem const& Assembly::append(AssemblyItem const& _i)
|
||||
return m_items.back();
|
||||
}
|
||||
|
||||
unsigned Assembly::bytesRequired(unsigned subTagSize) const
|
||||
unsigned Assembly::codeSize(unsigned subTagSize) const
|
||||
{
|
||||
for (unsigned tagSize = subTagSize; true; ++tagSize)
|
||||
{
|
||||
@ -65,7 +69,7 @@ unsigned Assembly::bytesRequired(unsigned subTagSize) const
|
||||
|
||||
for (AssemblyItem const& i: m_items)
|
||||
ret += i.bytesRequired(tagSize);
|
||||
if (util::bytesRequired(ret) <= tagSize)
|
||||
if (numberEncodingSize(ret) <= tagSize)
|
||||
return static_cast<unsigned>(ret);
|
||||
}
|
||||
}
|
||||
@ -82,16 +86,7 @@ string locationFromSources(StringMap const& _sourceCodes, SourceLocation const&
|
||||
if (it == _sourceCodes.end())
|
||||
return {};
|
||||
|
||||
string const& source = it->second;
|
||||
if (static_cast<size_t>(_location.start) >= source.size())
|
||||
return {};
|
||||
|
||||
string cut = source.substr(static_cast<size_t>(_location.start), static_cast<size_t>(_location.end - _location.start));
|
||||
auto newLinePos = cut.find_first_of("\n");
|
||||
if (newLinePos != string::npos)
|
||||
cut = cut.substr(0, newLinePos) + "...";
|
||||
|
||||
return cut;
|
||||
return CharStream::singleLineSnippet(it->second, _location);
|
||||
}
|
||||
|
||||
class Functionalizer
|
||||
@ -185,7 +180,7 @@ void Assembly::assemblyStream(ostream& _out, string const& _prefix, StringMap co
|
||||
_out << _prefix << "stop" << endl;
|
||||
for (auto const& i: m_data)
|
||||
if (u256(i.first) >= m_subs.size())
|
||||
_out << _prefix << "data_" << toHex(u256(i.first)) << " " << toHex(i.second) << endl;
|
||||
_out << _prefix << "data_" << toHex(u256(i.first)) << " " << util::toHex(i.second) << endl;
|
||||
|
||||
for (size_t i = 0; i < m_subs.size(); ++i)
|
||||
{
|
||||
@ -196,7 +191,7 @@ void Assembly::assemblyStream(ostream& _out, string const& _prefix, StringMap co
|
||||
}
|
||||
|
||||
if (m_auxiliaryData.size() > 0)
|
||||
_out << endl << _prefix << "auxdata: 0x" << toHex(m_auxiliaryData) << endl;
|
||||
_out << endl << _prefix << "auxdata: 0x" << util::toHex(m_auxiliaryData) << endl;
|
||||
}
|
||||
|
||||
string Assembly::assemblyString(StringMap const& _sourceCodes) const
|
||||
@ -208,7 +203,7 @@ string Assembly::assemblyString(StringMap const& _sourceCodes) const
|
||||
|
||||
Json::Value Assembly::createJsonValue(string _name, int _source, int _begin, int _end, string _value, string _jumpType)
|
||||
{
|
||||
Json::Value value;
|
||||
Json::Value value{Json::objectValue};
|
||||
value["name"] = _name;
|
||||
value["source"] = _source;
|
||||
value["begin"] = _begin;
|
||||
@ -230,8 +225,9 @@ string Assembly::toStringInHex(u256 _value)
|
||||
Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices) const
|
||||
{
|
||||
Json::Value root;
|
||||
root[".code"] = Json::arrayValue;
|
||||
|
||||
Json::Value& collection = root[".code"] = Json::arrayValue;
|
||||
Json::Value& collection = root[".code"];
|
||||
for (AssemblyItem const& i: m_items)
|
||||
{
|
||||
int sourceIndex = -1;
|
||||
@ -258,10 +254,6 @@ Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices)
|
||||
collection.append(
|
||||
createJsonValue("PUSH", sourceIndex, i.location().start, i.location().end, toStringInHex(i.data()), i.getJumpTypeAsString()));
|
||||
break;
|
||||
case PushString:
|
||||
collection.append(
|
||||
createJsonValue("PUSH tag", sourceIndex, i.location().start, i.location().end, m_strings.at(h256(i.data()))));
|
||||
break;
|
||||
case PushTag:
|
||||
if (i.data() == 0)
|
||||
collection.append(
|
||||
@ -320,7 +312,7 @@ Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices)
|
||||
collection.append(createJsonValue("PUSH data", sourceIndex, i.location().start, i.location().end, toStringInHex(i.data())));
|
||||
break;
|
||||
case VerbatimBytecode:
|
||||
collection.append(createJsonValue("VERBATIM", sourceIndex, i.location().start, i.location().end, toHex(i.verbatimData())));
|
||||
collection.append(createJsonValue("VERBATIM", sourceIndex, i.location().start, i.location().end, util::toHex(i.verbatimData())));
|
||||
break;
|
||||
default:
|
||||
assertThrow(false, InvalidOpcode, "");
|
||||
@ -329,10 +321,11 @@ Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices)
|
||||
|
||||
if (!m_data.empty() || !m_subs.empty())
|
||||
{
|
||||
Json::Value& data = root[".data"] = Json::objectValue;
|
||||
root[".data"] = Json::objectValue;
|
||||
Json::Value& data = root[".data"];
|
||||
for (auto const& i: m_data)
|
||||
if (u256(i.first) >= m_subs.size())
|
||||
data[toStringInHex((u256)i.first)] = toHex(i.second);
|
||||
data[toStringInHex((u256)i.first)] = util::toHex(i.second);
|
||||
|
||||
for (size_t i = 0; i < m_subs.size(); ++i)
|
||||
{
|
||||
@ -343,7 +336,7 @@ Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices)
|
||||
}
|
||||
|
||||
if (m_auxiliaryData.size() > 0)
|
||||
root[".auxdata"] = toHex(m_auxiliaryData);
|
||||
root[".auxdata"] = util::toHex(m_auxiliaryData);
|
||||
|
||||
return root;
|
||||
}
|
||||
@ -409,18 +402,21 @@ Assembly& Assembly::optimise(OptimiserSettings const& _settings)
|
||||
return *this;
|
||||
}
|
||||
|
||||
map<u256, u256> Assembly::optimiseInternal(
|
||||
map<u256, u256> const& Assembly::optimiseInternal(
|
||||
OptimiserSettings const& _settings,
|
||||
std::set<size_t> _tagsReferencedFromOutside
|
||||
)
|
||||
{
|
||||
if (m_tagReplacements)
|
||||
return *m_tagReplacements;
|
||||
|
||||
// Run optimisation for sub-assemblies.
|
||||
for (size_t subId = 0; subId < m_subs.size(); ++subId)
|
||||
{
|
||||
OptimiserSettings settings = _settings;
|
||||
// Disable creation mode for sub-assemblies.
|
||||
settings.isCreation = false;
|
||||
map<u256, u256> subTagReplacements = m_subs[subId]->optimiseInternal(
|
||||
map<u256, u256> const& subTagReplacements = m_subs[subId]->optimiseInternal(
|
||||
settings,
|
||||
JumpdestRemover::referencedTags(m_items, subId)
|
||||
);
|
||||
@ -546,7 +542,8 @@ map<u256, u256> Assembly::optimiseInternal(
|
||||
*this
|
||||
);
|
||||
|
||||
return tagReplacements;
|
||||
m_tagReplacements = move(tagReplacements);
|
||||
return *m_tagReplacements;
|
||||
}
|
||||
|
||||
LinkerObject const& Assembly::assemble() const
|
||||
@ -597,20 +594,20 @@ LinkerObject const& Assembly::assemble() const
|
||||
"Cannot push and assign immutables in the same assembly subroutine."
|
||||
);
|
||||
|
||||
unsigned bytesRequiredForCode = bytesRequired(static_cast<unsigned>(subTagSize));
|
||||
unsigned bytesRequiredForCode = codeSize(static_cast<unsigned>(subTagSize));
|
||||
m_tagPositionsInBytecode = vector<size_t>(m_usedTags, numeric_limits<size_t>::max());
|
||||
map<size_t, pair<size_t, size_t>> tagRef;
|
||||
multimap<h256, unsigned> dataRef;
|
||||
multimap<size_t, size_t> subRef;
|
||||
vector<unsigned> sizeRef; ///< Pointers to code locations where the size of the program is inserted
|
||||
unsigned bytesPerTag = util::bytesRequired(bytesRequiredForCode);
|
||||
unsigned bytesPerTag = numberEncodingSize(bytesRequiredForCode);
|
||||
uint8_t tagPush = static_cast<uint8_t>(pushInstruction(bytesPerTag));
|
||||
|
||||
unsigned bytesRequiredIncludingData = bytesRequiredForCode + 1 + static_cast<unsigned>(m_auxiliaryData.size());
|
||||
for (auto const& sub: m_subs)
|
||||
bytesRequiredIncludingData += static_cast<unsigned>(sub->assemble().bytecode.size());
|
||||
|
||||
unsigned bytesPerDataRef = util::bytesRequired(bytesRequiredIncludingData);
|
||||
unsigned bytesPerDataRef = numberEncodingSize(bytesRequiredIncludingData);
|
||||
uint8_t dataRefPush = static_cast<uint8_t>(pushInstruction(bytesPerDataRef));
|
||||
ret.bytecode.reserve(bytesRequiredIncludingData);
|
||||
|
||||
@ -625,22 +622,9 @@ LinkerObject const& Assembly::assemble() const
|
||||
case Operation:
|
||||
ret.bytecode.push_back(static_cast<uint8_t>(i.instruction()));
|
||||
break;
|
||||
case PushString:
|
||||
{
|
||||
ret.bytecode.push_back(static_cast<uint8_t>(Instruction::PUSH32));
|
||||
unsigned ii = 0;
|
||||
for (auto j: m_strings.at(h256(i.data())))
|
||||
if (++ii > 32)
|
||||
break;
|
||||
else
|
||||
ret.bytecode.push_back(uint8_t(j));
|
||||
while (ii++ < 32)
|
||||
ret.bytecode.push_back(0);
|
||||
break;
|
||||
}
|
||||
case Push:
|
||||
{
|
||||
unsigned b = max<unsigned>(1, util::bytesRequired(i.data()));
|
||||
unsigned b = max<unsigned>(1, numberEncodingSize(i.data()));
|
||||
ret.bytecode.push_back(static_cast<uint8_t>(pushInstruction(b)));
|
||||
ret.bytecode.resize(ret.bytecode.size() + b);
|
||||
bytesRef byr(&ret.bytecode.back() + 1 - b, b);
|
||||
@ -670,7 +654,7 @@ LinkerObject const& Assembly::assemble() const
|
||||
assertThrow(i.data() <= numeric_limits<size_t>::max(), AssemblyException, "");
|
||||
auto s = subAssemblyById(static_cast<size_t>(i.data()))->assemble().bytecode.size();
|
||||
i.setPushedValue(u256(s));
|
||||
unsigned b = max<unsigned>(1, util::bytesRequired(s));
|
||||
unsigned b = max<unsigned>(1, numberEncodingSize(s));
|
||||
ret.bytecode.push_back(static_cast<uint8_t>(pushInstruction(b)));
|
||||
ret.bytecode.resize(ret.bytecode.size() + b);
|
||||
bytesRef byr(&ret.bytecode.back() + 1 - b, b);
|
||||
@ -775,15 +759,23 @@ LinkerObject const& Assembly::assemble() const
|
||||
assertThrow(tagId < tagPositions.size(), AssemblyException, "Reference to non-existing tag.");
|
||||
size_t pos = tagPositions[tagId];
|
||||
assertThrow(pos != numeric_limits<size_t>::max(), AssemblyException, "Reference to tag without position.");
|
||||
assertThrow(util::bytesRequired(pos) <= bytesPerTag, AssemblyException, "Tag too large for reserved space.");
|
||||
assertThrow(numberEncodingSize(pos) <= bytesPerTag, AssemblyException, "Tag too large for reserved space.");
|
||||
bytesRef r(ret.bytecode.data() + i.first, bytesPerTag);
|
||||
toBigEndian(pos, r);
|
||||
}
|
||||
for (auto const& [name, tagInfo]: m_namedTags)
|
||||
{
|
||||
size_t position = m_tagPositionsInBytecode.at(tagInfo.id);
|
||||
optional<size_t> tagIndex;
|
||||
for (auto&& [index, item]: m_items | ranges::views::enumerate)
|
||||
if (item.type() == Tag && static_cast<size_t>(item.data()) == tagInfo.id)
|
||||
{
|
||||
tagIndex = index;
|
||||
break;
|
||||
}
|
||||
ret.functionDebugData[name] = {
|
||||
position == numeric_limits<size_t>::max() ? nullopt : optional<size_t>{position},
|
||||
tagIndex,
|
||||
tagInfo.sourceID,
|
||||
tagInfo.params,
|
||||
tagInfo.returns
|
||||
|
@ -165,9 +165,9 @@ protected:
|
||||
/// Does the same operations as @a optimise, but should only be applied to a sub and
|
||||
/// returns the replaced tags. Also takes an argument containing the tags of this assembly
|
||||
/// that are referenced in a super-assembly.
|
||||
std::map<u256, u256> optimiseInternal(OptimiserSettings const& _settings, std::set<size_t> _tagsReferencedFromOutside);
|
||||
std::map<u256, u256> const& optimiseInternal(OptimiserSettings const& _settings, std::set<size_t> _tagsReferencedFromOutside);
|
||||
|
||||
unsigned bytesRequired(unsigned subTagSize) const;
|
||||
unsigned codeSize(unsigned subTagSize) const;
|
||||
|
||||
private:
|
||||
static Json::Value createJsonValue(
|
||||
@ -210,6 +210,10 @@ protected:
|
||||
/// This map is used only for sub-assemblies which are not direct sub-assemblies (where path is having more than one value).
|
||||
std::map<std::vector<size_t>, size_t> m_subPaths;
|
||||
|
||||
/// Contains the tag replacements relevant for super-assemblies.
|
||||
/// If set, it means the optimizer has run and we will not run it again.
|
||||
std::optional<std::map<u256, u256>> m_tagReplacements;
|
||||
|
||||
mutable LinkerObject m_assembledObject;
|
||||
mutable std::vector<size_t> m_tagPositionsInBytecode;
|
||||
|
||||
|
@ -21,11 +21,13 @@
|
||||
#include <libevmasm/Assembly.h>
|
||||
|
||||
#include <libsolutil/CommonData.h>
|
||||
#include <libsolutil/Numeric.h>
|
||||
#include <libsolutil/StringUtils.h>
|
||||
#include <libsolutil/FixedHash.h>
|
||||
#include <liblangutil/SourceLocation.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
@ -70,10 +72,8 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength) const
|
||||
case Operation:
|
||||
case Tag: // 1 byte for the JUMPDEST
|
||||
return 1;
|
||||
case PushString:
|
||||
return 1 + 32;
|
||||
case Push:
|
||||
return 1 + max<size_t>(1, util::bytesRequired(data()));
|
||||
return 1 + max<size_t>(1, numberEncodingSize(data()));
|
||||
case PushSubSize:
|
||||
case PushProgramSize:
|
||||
return 1 + 4; // worst case: a 16MB program
|
||||
@ -118,7 +118,6 @@ size_t AssemblyItem::returnValues() const
|
||||
case Operation:
|
||||
return static_cast<size_t>(instructionInfo(instruction()).ret);
|
||||
case Push:
|
||||
case PushString:
|
||||
case PushTag:
|
||||
case PushData:
|
||||
case PushSub:
|
||||
@ -147,7 +146,6 @@ bool AssemblyItem::canBeFunctional() const
|
||||
case Operation:
|
||||
return !isDupInstruction(instruction()) && !isSwapInstruction(instruction());
|
||||
case Push:
|
||||
case PushString:
|
||||
case PushTag:
|
||||
case PushData:
|
||||
case PushSub:
|
||||
@ -193,10 +191,7 @@ string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
|
||||
break;
|
||||
}
|
||||
case Push:
|
||||
text = toHex(util::toCompactBigEndian(data(), 1), util::HexPrefix::Add);
|
||||
break;
|
||||
case PushString:
|
||||
text = string("data_") + util::toHex(data());
|
||||
text = toHex(toCompactBigEndian(data(), 1), util::HexPrefix::Add);
|
||||
break;
|
||||
case PushTag:
|
||||
{
|
||||
@ -214,7 +209,7 @@ string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
|
||||
text = string("tag_") + to_string(static_cast<size_t>(data())) + ":";
|
||||
break;
|
||||
case PushData:
|
||||
text = string("data_") + util::toHex(data());
|
||||
text = string("data_") + toHex(data());
|
||||
break;
|
||||
case PushSub:
|
||||
case PushSubSize:
|
||||
@ -233,16 +228,16 @@ string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
|
||||
text = string("bytecodeSize");
|
||||
break;
|
||||
case PushLibraryAddress:
|
||||
text = string("linkerSymbol(\"") + util::toHex(data()) + string("\")");
|
||||
text = string("linkerSymbol(\"") + toHex(data()) + string("\")");
|
||||
break;
|
||||
case PushDeployTimeAddress:
|
||||
text = string("deployTimeAddress()");
|
||||
break;
|
||||
case PushImmutable:
|
||||
text = string("immutable(\"") + toHex(util::toCompactBigEndian(data(), 1), util::HexPrefix::Add) + "\")";
|
||||
text = string("immutable(\"") + "0x" + util::toHex(toCompactBigEndian(data(), 1)) + "\")";
|
||||
break;
|
||||
case AssignImmutable:
|
||||
text = string("assignImmutable(\"") + toHex(util::toCompactBigEndian(data(), 1), util::HexPrefix::Add) + "\")";
|
||||
text = string("assignImmutable(\"") + "0x" + util::toHex(toCompactBigEndian(data(), 1)) + "\")";
|
||||
break;
|
||||
case UndefinedItem:
|
||||
assertThrow(false, AssemblyException, "Invalid assembly item.");
|
||||
@ -276,9 +271,6 @@ ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item)
|
||||
case Push:
|
||||
_out << " PUSH " << hex << _item.data() << dec;
|
||||
break;
|
||||
case PushString:
|
||||
_out << " PushString" << hex << (unsigned)_item.data() << dec;
|
||||
break;
|
||||
case PushTag:
|
||||
{
|
||||
size_t subId = _item.splitForeignPushTag().first;
|
||||
|
@ -38,7 +38,6 @@ enum AssemblyItemType
|
||||
UndefinedItem,
|
||||
Operation,
|
||||
Push,
|
||||
PushString,
|
||||
PushTag,
|
||||
PushSub,
|
||||
PushSubSize,
|
||||
|
@ -25,6 +25,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <libsolutil/Common.h>
|
||||
#include <libsolutil/Numeric.h>
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
@ -64,9 +66,14 @@ private:
|
||||
/// Iterator that skips tags and skips to the end if (all branches of) the control
|
||||
/// flow does not continue to the next instruction.
|
||||
/// If the arguments are supplied to the constructor, replaces items on the fly.
|
||||
struct BlockIterator: std::iterator<std::forward_iterator_tag, AssemblyItem const>
|
||||
struct BlockIterator
|
||||
{
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = AssemblyItem const;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = AssemblyItem const*;
|
||||
using reference = AssemblyItem const&;
|
||||
BlockIterator(
|
||||
AssemblyItems::const_iterator _it,
|
||||
AssemblyItems::const_iterator _end,
|
||||
|
@ -133,7 +133,7 @@ bigint LiteralMethod::gasNeeded() const
|
||||
return combineGas(
|
||||
simpleRunGas({Instruction::PUSH1}),
|
||||
// PUSHX plus data
|
||||
(m_params.isCreation ? GasCosts::txDataNonZeroGas(m_params.evmVersion) : GasCosts::createDataGas) + dataGas(util::toCompactBigEndian(m_value, 1)),
|
||||
(m_params.isCreation ? GasCosts::txDataNonZeroGas(m_params.evmVersion) : GasCosts::createDataGas) + dataGas(toCompactBigEndian(m_value, 1)),
|
||||
0
|
||||
);
|
||||
}
|
||||
@ -146,13 +146,13 @@ bigint CodeCopyMethod::gasNeeded() const
|
||||
// Data gas for copy routines: Some bytes are zero, but we ignore them.
|
||||
bytesRequired(copyRoutine()) * (m_params.isCreation ? GasCosts::txDataNonZeroGas(m_params.evmVersion) : GasCosts::createDataGas),
|
||||
// Data gas for data itself
|
||||
dataGas(util::toBigEndian(m_value))
|
||||
dataGas(toBigEndian(m_value))
|
||||
);
|
||||
}
|
||||
|
||||
AssemblyItems CodeCopyMethod::execute(Assembly& _assembly) const
|
||||
{
|
||||
bytes data = util::toBigEndian(m_value);
|
||||
bytes data = toBigEndian(m_value);
|
||||
assertThrow(data.size() == 32, OptimizerException, "Invalid number encoding.");
|
||||
AssemblyItems actualCopyRoutine = copyRoutine();
|
||||
actualCopyRoutine[4] = _assembly.newData(data);
|
||||
@ -192,7 +192,7 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
|
||||
if (_value < 0x10000)
|
||||
// Very small value, not worth computing
|
||||
return AssemblyItems{_value};
|
||||
else if (util::bytesRequired(~_value) < util::bytesRequired(_value))
|
||||
else if (numberEncodingSize(~_value) < numberEncodingSize(_value))
|
||||
// Negated is shorter to represent
|
||||
return findRepresentation(~_value) + AssemblyItems{Instruction::NOT};
|
||||
else
|
||||
|
@ -24,12 +24,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <libsolutil/Common.h>
|
||||
#include <libsolutil/Assertions.h>
|
||||
#include <libevmasm/ExpressionClasses.h>
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
|
||||
namespace solidity::evmasm
|
||||
{
|
||||
|
||||
|
@ -29,6 +29,7 @@
|
||||
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
#include <limits>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
|
@ -47,7 +47,6 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
|
||||
case Push:
|
||||
case PushTag:
|
||||
case PushData:
|
||||
case PushString:
|
||||
case PushSub:
|
||||
case PushSubSize:
|
||||
case PushProgramSize:
|
||||
|
@ -36,7 +36,7 @@
|
||||
#include <range/v3/view/transform.hpp>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <limits>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <libevmasm/Exceptions.h>
|
||||
#include <libsolutil/Common.h>
|
||||
#include <libsolutil/Assertions.h>
|
||||
#include <libsolutil/Numeric.h>
|
||||
#include <functional>
|
||||
|
||||
namespace solidity::evmasm
|
||||
@ -301,7 +302,7 @@ bool isValidInstruction(Instruction _inst);
|
||||
extern const std::map<std::string, Instruction> c_instructions;
|
||||
|
||||
/// Iterate through EVM code and call a function on each instruction.
|
||||
void eachInstruction(bytes const& _mem, std::function<void(Instruction,u256 const&)> const& _onInstruction);
|
||||
void eachInstruction(bytes const& _mem, std::function<void(Instruction, u256 const&)> const& _onInstruction);
|
||||
|
||||
/// Convert from EVM code to simple EVM assembly language.
|
||||
std::string disassemble(bytes const& _mem, std::string const& _delimiter = " ");
|
||||
|
@ -24,6 +24,8 @@
|
||||
|
||||
#include <libevmasm/AssemblyItem.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::util;
|
||||
|
@ -415,7 +415,7 @@ KnownState::Id KnownState::applyKeccak256(
|
||||
{
|
||||
bytes data;
|
||||
for (Id a: arguments)
|
||||
data += util::toBigEndian(*m_expressionClasses->knownConstant(a));
|
||||
data += toBigEndian(*m_expressionClasses->knownConstant(a));
|
||||
data.resize(length);
|
||||
v = m_expressionClasses->find(AssemblyItem(u256(util::keccak256(data)), _location));
|
||||
}
|
||||
|
@ -24,14 +24,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wredeclared-class-member"
|
||||
@ -58,6 +50,15 @@
|
||||
#include <libevmasm/ExpressionClasses.h>
|
||||
#include <libevmasm/SemanticInformation.h>
|
||||
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
|
||||
namespace solidity::langutil
|
||||
{
|
||||
struct SourceLocation;
|
||||
|
@ -48,6 +48,7 @@ struct LinkerObject
|
||||
struct FunctionDebugData
|
||||
{
|
||||
std::optional<size_t> bytecodeOffset;
|
||||
std::optional<size_t> instructionIndex;
|
||||
std::optional<size_t> sourceID;
|
||||
size_t params = {};
|
||||
size_t returns = {};
|
||||
|
@ -112,7 +112,7 @@ struct PushPop: SimplePeepholeOptimizerMethod<PushPop, 2>
|
||||
auto t = _push.type();
|
||||
return _pop == Instruction::POP && (
|
||||
SemanticInformation::isDupInstruction(_push) ||
|
||||
t == Push || t == PushString || t == PushTag || t == PushSub ||
|
||||
t == Push || t == PushTag || t == PushSub ||
|
||||
t == PushSubSize || t == PushProgramSize || t == PushData || t == PushLibraryAddress
|
||||
);
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ template <class S> S modWorkaround(S const& _a, S const& _b)
|
||||
}
|
||||
|
||||
// This works around a bug fixed with Boost 1.64.
|
||||
// https://www.boost.org/doc/libs/1_68_0/libs/multiprecision/doc/html/boost_multiprecision/map/hist.html#boost_multiprecision.map.hist.multiprecision_2_3_1_boost_1_64
|
||||
// https://www.boost.org/doc/libs/release/libs/multiprecision/doc/html/boost_multiprecision/map/hist.html#boost_multiprecision.map.hist.multiprecision_2_3_1_boost_1_64
|
||||
template <class S> S shlWorkaround(S const& _x, unsigned _amount)
|
||||
{
|
||||
return u256((bigint(_x) << _amount) & u256(-1));
|
||||
|
@ -41,7 +41,6 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool
|
||||
case VerbatimBytecode:
|
||||
return true;
|
||||
case Push:
|
||||
case PushString:
|
||||
case PushTag:
|
||||
case PushSub:
|
||||
case PushSubSize:
|
||||
|
@ -128,3 +128,19 @@ string_view CharStream::text(SourceLocation const& _location) const
|
||||
static_cast<size_t>(_location.end - _location.start)
|
||||
);
|
||||
}
|
||||
|
||||
string CharStream::singleLineSnippet(string const& _sourceCode, SourceLocation const& _location)
|
||||
{
|
||||
if (!_location.hasText())
|
||||
return {};
|
||||
|
||||
if (static_cast<size_t>(_location.start) >= _sourceCode.size())
|
||||
return {};
|
||||
|
||||
string cut = _sourceCode.substr(static_cast<size_t>(_location.start), static_cast<size_t>(_location.end - _location.start));
|
||||
auto newLinePos = cut.find_first_of("\n\r");
|
||||
if (newLinePos != string::npos)
|
||||
cut = cut.substr(0, newLinePos) + "...";
|
||||
|
||||
return cut;
|
||||
}
|
||||
|
@ -118,6 +118,15 @@ public:
|
||||
/// Returns an empty string view if the source location does not `hasText()`.
|
||||
std::string_view text(SourceLocation const& _location) const;
|
||||
|
||||
/// @returns the first line of the referenced source fragment. If the fragment is longer than
|
||||
/// one line, appends an ellipsis to indicate that.
|
||||
std::string singleLineSnippet(SourceLocation const& _location) const
|
||||
{
|
||||
return singleLineSnippet(m_source, _location);
|
||||
}
|
||||
|
||||
static std::string singleLineSnippet(std::string const& _sourceCode, SourceLocation const& _location);
|
||||
|
||||
private:
|
||||
std::string m_source;
|
||||
std::string m_name;
|
||||
|
@ -94,6 +94,16 @@ bool ErrorReporter::checkForExcessiveErrors(Error::Type _type)
|
||||
if (m_warningCount >= c_maxWarningsAllowed)
|
||||
return true;
|
||||
}
|
||||
else if (_type == Error::Type::Info)
|
||||
{
|
||||
m_infoCount++;
|
||||
|
||||
if (m_infoCount == c_maxInfosAllowed)
|
||||
m_errorList.push_back(make_shared<Error>(2833_error, Error::Type::Info, "There are more than 256 infos. Ignoring the rest."));
|
||||
|
||||
if (m_infoCount >= c_maxInfosAllowed)
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_errorCount++;
|
||||
@ -242,3 +252,12 @@ void ErrorReporter::docstringParsingError(ErrorId _error, SourceLocation const&
|
||||
_description
|
||||
);
|
||||
}
|
||||
|
||||
void ErrorReporter::info(
|
||||
ErrorId _error,
|
||||
SourceLocation const& _location,
|
||||
string const& _description
|
||||
)
|
||||
{
|
||||
error(_error, Error::Type::Info, _location, _description);
|
||||
}
|
||||
|
@ -63,6 +63,8 @@ public:
|
||||
SecondarySourceLocation const& _secondaryLocation
|
||||
);
|
||||
|
||||
void info(ErrorId _error, SourceLocation const& _location, std::string const& _description);
|
||||
|
||||
void error(
|
||||
ErrorId _error,
|
||||
Error::Type _type,
|
||||
@ -118,13 +120,13 @@ public:
|
||||
|
||||
void clear();
|
||||
|
||||
/// @returns true iff there is any error (ignores warnings).
|
||||
/// @returns true iff there is any error (ignores warnings and infos).
|
||||
bool hasErrors() const
|
||||
{
|
||||
return m_errorCount > 0;
|
||||
}
|
||||
|
||||
/// @returns the number of errors (ignores warnings).
|
||||
/// @returns the number of errors (ignores warnings and infos).
|
||||
unsigned errorCount() const
|
||||
{
|
||||
return m_errorCount;
|
||||
@ -183,9 +185,11 @@ private:
|
||||
|
||||
unsigned m_errorCount = 0;
|
||||
unsigned m_warningCount = 0;
|
||||
unsigned m_infoCount = 0;
|
||||
|
||||
unsigned const c_maxWarningsAllowed = 256;
|
||||
unsigned const c_maxErrorsAllowed = 256;
|
||||
unsigned const c_maxInfosAllowed = 256;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -47,6 +47,9 @@ Error::Error(
|
||||
case Type::DocstringParsingError:
|
||||
m_typeName = "DocstringParsingError";
|
||||
break;
|
||||
case Type::Info:
|
||||
m_typeName = "Info";
|
||||
break;
|
||||
case Type::ParserError:
|
||||
m_typeName = "ParserError";
|
||||
break;
|
||||
|
@ -28,6 +28,10 @@
|
||||
#include <libsolutil/CommonData.h>
|
||||
#include <liblangutil/SourceLocation.h>
|
||||
|
||||
#include <boost/preprocessor/cat.hpp>
|
||||
#include <boost/preprocessor/facilities/empty.hpp>
|
||||
#include <boost/preprocessor/facilities/overload.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@ -45,18 +49,68 @@ struct FatalError: virtual util::Exception {};
|
||||
struct UnimplementedFeatureError: virtual util::Exception {};
|
||||
struct InvalidAstError: virtual util::Exception {};
|
||||
|
||||
|
||||
/// Assertion that throws an InternalCompilerError containing the given description if it is not met.
|
||||
#define solAssert(CONDITION, DESCRIPTION) \
|
||||
assertThrow(CONDITION, ::solidity::langutil::InternalCompilerError, DESCRIPTION)
|
||||
#if !BOOST_PP_VARIADICS_MSVC
|
||||
#define solAssert(...) BOOST_PP_OVERLOAD(solAssert_,__VA_ARGS__)(__VA_ARGS__)
|
||||
#else
|
||||
#define solAssert(...) BOOST_PP_CAT(BOOST_PP_OVERLOAD(solAssert_,__VA_ARGS__)(__VA_ARGS__),BOOST_PP_EMPTY())
|
||||
#endif
|
||||
|
||||
#define solUnimplementedAssert(CONDITION, DESCRIPTION) \
|
||||
assertThrow(CONDITION, ::solidity::langutil::UnimplementedFeatureError, DESCRIPTION)
|
||||
#define solAssert_1(CONDITION) \
|
||||
solAssert_2(CONDITION, "")
|
||||
|
||||
#define solAssert_2(CONDITION, DESCRIPTION) \
|
||||
assertThrowWithDefaultDescription( \
|
||||
CONDITION, \
|
||||
::solidity::langutil::InternalCompilerError, \
|
||||
DESCRIPTION, \
|
||||
"Solidity assertion failed" \
|
||||
)
|
||||
|
||||
|
||||
/// Assertion that throws an UnimplementedFeatureError containing the given description if it is not met.
|
||||
#if !BOOST_PP_VARIADICS_MSVC
|
||||
#define solUnimplementedAssert(...) BOOST_PP_OVERLOAD(solUnimplementedAssert_,__VA_ARGS__)(__VA_ARGS__)
|
||||
#else
|
||||
#define solUnimplementedAssert(...) BOOST_PP_CAT(BOOST_PP_OVERLOAD(solUnimplementedAssert_,__VA_ARGS__)(__VA_ARGS__),BOOST_PP_EMPTY())
|
||||
#endif
|
||||
|
||||
#define solUnimplementedAssert_1(CONDITION) \
|
||||
solUnimplementedAssert_2(CONDITION, "")
|
||||
|
||||
#define solUnimplementedAssert_2(CONDITION, DESCRIPTION) \
|
||||
assertThrowWithDefaultDescription( \
|
||||
CONDITION, \
|
||||
::solidity::langutil::UnimplementedFeatureError, \
|
||||
DESCRIPTION, \
|
||||
"Unimplemented feature" \
|
||||
)
|
||||
|
||||
|
||||
/// Helper that unconditionally reports an unimplemented feature.
|
||||
#define solUnimplemented(DESCRIPTION) \
|
||||
solUnimplementedAssert(false, DESCRIPTION)
|
||||
|
||||
#define astAssert(CONDITION, DESCRIPTION) \
|
||||
assertThrow(CONDITION, ::solidity::langutil::InvalidAstError, DESCRIPTION)
|
||||
|
||||
/// Assertion that throws an InvalidAstError containing the given description if it is not met.
|
||||
#if !BOOST_PP_VARIADICS_MSVC
|
||||
#define astAssert(...) BOOST_PP_OVERLOAD(astAssert_,__VA_ARGS__)(__VA_ARGS__)
|
||||
#else
|
||||
#define astAssert(...) BOOST_PP_CAT(BOOST_PP_OVERLOAD(astAssert_,__VA_ARGS__)(__VA_ARGS__),BOOST_PP_EMPTY())
|
||||
#endif
|
||||
|
||||
#define astAssert_1(CONDITION) \
|
||||
astAssert_2(CONDITION, "")
|
||||
|
||||
#define astAssert_2(CONDITION, DESCRIPTION) \
|
||||
assertThrowWithDefaultDescription( \
|
||||
CONDITION, \
|
||||
::solidity::langutil::InvalidAstError, \
|
||||
DESCRIPTION, \
|
||||
"AST assertion failed" \
|
||||
)
|
||||
|
||||
|
||||
using errorSourceLocationInfo = std::pair<std::string, SourceLocation>;
|
||||
|
||||
@ -120,7 +174,15 @@ public:
|
||||
ParserError,
|
||||
TypeError,
|
||||
SyntaxError,
|
||||
Warning
|
||||
Warning,
|
||||
Info
|
||||
};
|
||||
|
||||
enum class Severity
|
||||
{
|
||||
Error,
|
||||
Warning,
|
||||
Info
|
||||
};
|
||||
|
||||
Error(
|
||||
@ -139,21 +201,63 @@ public:
|
||||
static Error const* containsErrorOfType(ErrorList const& _list, Error::Type _type)
|
||||
{
|
||||
for (auto e: _list)
|
||||
{
|
||||
if (e->type() == _type)
|
||||
return e.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
static bool containsOnlyWarnings(ErrorList const& _list)
|
||||
|
||||
static Severity errorSeverity(Type _type)
|
||||
{
|
||||
if (_type == Type::Info)
|
||||
return Severity::Info;
|
||||
if (_type == Type::Warning)
|
||||
return Severity::Warning;
|
||||
return Severity::Error;
|
||||
}
|
||||
|
||||
static bool isError(Severity _severity)
|
||||
{
|
||||
return _severity == Severity::Error;
|
||||
}
|
||||
|
||||
static bool isError(Type _type)
|
||||
{
|
||||
return isError(errorSeverity(_type));
|
||||
}
|
||||
|
||||
static bool containsErrors(ErrorList const& _list)
|
||||
{
|
||||
for (auto e: _list)
|
||||
{
|
||||
if (e->type() != Type::Warning)
|
||||
if (isError(e->type()))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
static std::string formatErrorSeverity(Severity _severity)
|
||||
{
|
||||
if (_severity == Severity::Info)
|
||||
return "Info";
|
||||
if (_severity == Severity::Warning)
|
||||
return "Warning";
|
||||
solAssert(isError(_severity), "");
|
||||
return "Error";
|
||||
}
|
||||
|
||||
static std::string formatErrorSeverityLowercase(Severity _severity)
|
||||
{
|
||||
switch (_severity)
|
||||
{
|
||||
case Severity::Info:
|
||||
return "info";
|
||||
case Severity::Warning:
|
||||
return "warning";
|
||||
case Severity::Error:
|
||||
solAssert(isError(_severity), "");
|
||||
return "error";
|
||||
}
|
||||
solAssert(false, "");
|
||||
}
|
||||
|
||||
private:
|
||||
ErrorId m_errorId;
|
||||
Type m_type;
|
||||
|
@ -104,7 +104,7 @@ protected:
|
||||
void fatalParserError(ErrorId _error, SourceLocation const& _location, std::string const& _description);
|
||||
|
||||
std::shared_ptr<Scanner> m_scanner;
|
||||
/// The reference to the list of errors and warning to add errors/warnings during parsing
|
||||
/// The reference to the list of errors, warnings and infos to add errors/warnings/infos during parsing
|
||||
ErrorReporter& m_errorReporter;
|
||||
/// Current recursion depth during parsing.
|
||||
size_t m_recursionDepth = 0;
|
||||
|
@ -59,6 +59,7 @@
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <array>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <liblangutil/Exceptions.h>
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
|
@ -30,7 +30,7 @@ using namespace solidity::langutil;
|
||||
SourceReferenceExtractor::Message SourceReferenceExtractor::extract(
|
||||
CharStreamProvider const& _charStreamProvider,
|
||||
util::Exception const& _exception,
|
||||
string _category
|
||||
string _severity
|
||||
)
|
||||
{
|
||||
SourceLocation const* location = boost::get_error_info<errinfo_sourceLocation>(_exception);
|
||||
@ -44,7 +44,7 @@ SourceReferenceExtractor::Message SourceReferenceExtractor::extract(
|
||||
for (auto const& info: secondaryLocation->infos)
|
||||
secondary.emplace_back(extract(_charStreamProvider, &info.second, info.first));
|
||||
|
||||
return Message{std::move(primary), _category, std::move(secondary), nullopt};
|
||||
return Message{std::move(primary), _severity, std::move(secondary), nullopt};
|
||||
}
|
||||
|
||||
SourceReferenceExtractor::Message SourceReferenceExtractor::extract(
|
||||
@ -52,8 +52,8 @@ SourceReferenceExtractor::Message SourceReferenceExtractor::extract(
|
||||
Error const& _error
|
||||
)
|
||||
{
|
||||
string category = (_error.type() == Error::Type::Warning) ? "Warning" : "Error";
|
||||
Message message = extract(_charStreamProvider, _error, category);
|
||||
string severity = Error::formatErrorSeverity(Error::errorSeverity(_error.type()));
|
||||
Message message = extract(_charStreamProvider, _error, severity);
|
||||
message.errorId = _error.errorId();
|
||||
return message;
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ struct LineColumn
|
||||
|
||||
struct SourceReference
|
||||
{
|
||||
std::string message; ///< A message that relates to this source reference (such as a warning or an error message).
|
||||
std::string message; ///< A message that relates to this source reference (such as a warning, info or an error message).
|
||||
std::string sourceName; ///< Underlying source name (for example the filename).
|
||||
LineColumn position; ///< Actual (error) position this source reference is surrounding.
|
||||
bool multiline = {false}; ///< Indicates whether the actual SourceReference is truncated to one line.
|
||||
@ -64,12 +64,12 @@ namespace SourceReferenceExtractor
|
||||
struct Message
|
||||
{
|
||||
SourceReference primary;
|
||||
std::string category; // "Error", "Warning", ...
|
||||
std::string severity; // "Error", "Warning", "Info", ...
|
||||
std::vector<SourceReference> secondary;
|
||||
std::optional<ErrorId> errorId;
|
||||
};
|
||||
|
||||
Message extract(CharStreamProvider const& _charStreamProvider, util::Exception const& _exception, std::string _category);
|
||||
Message extract(CharStreamProvider const& _charStreamProvider, util::Exception const& _exception, std::string _severity);
|
||||
Message extract(CharStreamProvider const& _charStreamProvider, Error const& _error);
|
||||
SourceReference extract(CharStreamProvider const& _charStreamProvider, SourceLocation const* _location, std::string message = "");
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ void SourceReferenceFormatter::printSourceLocation(SourceReference const& _ref)
|
||||
void SourceReferenceFormatter::printExceptionInformation(SourceReferenceExtractor::Message const& _msg)
|
||||
{
|
||||
// exception header line
|
||||
errorColored() << _msg.category;
|
||||
errorColored() << _msg.severity;
|
||||
if (m_withErrorIds && _msg.errorId.has_value())
|
||||
errorColored() << " (" << _msg.errorId.value().error << ")";
|
||||
messageColored() << ": " << _msg.primary.message << '\n';
|
||||
@ -181,9 +181,9 @@ void SourceReferenceFormatter::printExceptionInformation(SourceReferenceExtracto
|
||||
m_stream << '\n';
|
||||
}
|
||||
|
||||
void SourceReferenceFormatter::printExceptionInformation(util::Exception const& _exception, std::string const& _category)
|
||||
void SourceReferenceFormatter::printExceptionInformation(util::Exception const& _exception, std::string const& _severity)
|
||||
{
|
||||
printExceptionInformation(SourceReferenceExtractor::extract(m_charStreamProvider, _exception, _category));
|
||||
printExceptionInformation(SourceReferenceExtractor::extract(m_charStreamProvider, _exception, _severity));
|
||||
}
|
||||
|
||||
void SourceReferenceFormatter::printErrorInformation(ErrorList const& _errors)
|
||||
|
@ -52,7 +52,7 @@ public:
|
||||
/// Prints source location if it is given.
|
||||
void printSourceLocation(SourceReference const& _ref);
|
||||
void printExceptionInformation(SourceReferenceExtractor::Message const& _msg);
|
||||
void printExceptionInformation(util::Exception const& _exception, std::string const& _category);
|
||||
void printExceptionInformation(util::Exception const& _exception, std::string const& _severity);
|
||||
void printErrorInformation(langutil::ErrorList const& _errors);
|
||||
void printErrorInformation(Error const& _error);
|
||||
|
||||
@ -77,7 +77,7 @@ public:
|
||||
{
|
||||
return formatExceptionInformation(
|
||||
_error,
|
||||
(_error.type() == Error::Type::Warning) ? "Warning" : "Error",
|
||||
Error::formatErrorSeverity(Error::errorSeverity(_error.type())),
|
||||
_charStreamProvider
|
||||
);
|
||||
}
|
||||
|
@ -57,12 +57,8 @@ public:
|
||||
}
|
||||
|
||||
void warning(ErrorId _error, std::string const& _description)
|
||||
{
|
||||
if (!seen(_error, {}, _description))
|
||||
{
|
||||
m_errorReporter.warning(_error, _description);
|
||||
markAsSeen(_error, {}, _description);
|
||||
}
|
||||
}
|
||||
|
||||
bool seen(ErrorId _error, SourceLocation const& _location, std::string const& _description) const
|
||||
@ -77,7 +73,7 @@ public:
|
||||
|
||||
void markAsSeen(ErrorId _error, SourceLocation const& _location, std::string const& _description)
|
||||
{
|
||||
solAssert(!seen(_error, _location, _description), "");
|
||||
if (_location != SourceLocation{})
|
||||
m_seenErrors[{_error, _location}] = _description;
|
||||
}
|
||||
|
||||
|
@ -60,8 +60,8 @@ void CHCSmtLib2Interface::reset()
|
||||
|
||||
void CHCSmtLib2Interface::registerRelation(Expression const& _expr)
|
||||
{
|
||||
smtAssert(_expr.sort, "");
|
||||
smtAssert(_expr.sort->kind == Kind::Function, "");
|
||||
smtAssert(_expr.sort);
|
||||
smtAssert(_expr.sort->kind == Kind::Function);
|
||||
if (!m_variables.count(_expr.name))
|
||||
{
|
||||
auto fSort = dynamic_pointer_cast<FunctionSort>(_expr.sort);
|
||||
@ -124,7 +124,7 @@ pair<CheckResult, CHCSolverInterface::CexGraph> CHCSmtLib2Interface::query(Expre
|
||||
|
||||
void CHCSmtLib2Interface::declareVariable(string const& _name, SortPointer const& _sort)
|
||||
{
|
||||
smtAssert(_sort, "");
|
||||
smtAssert(_sort);
|
||||
if (_sort->kind == Kind::Function)
|
||||
declareFunction(_name, _sort);
|
||||
else if (!m_variables.count(_name))
|
||||
@ -172,13 +172,13 @@ string CHCSmtLib2Interface::forall()
|
||||
|
||||
void CHCSmtLib2Interface::declareFunction(string const& _name, SortPointer const& _sort)
|
||||
{
|
||||
smtAssert(_sort, "");
|
||||
smtAssert(_sort->kind == Kind::Function, "");
|
||||
smtAssert(_sort);
|
||||
smtAssert(_sort->kind == Kind::Function);
|
||||
// TODO Use domain and codomain as key as well
|
||||
if (!m_variables.count(_name))
|
||||
{
|
||||
auto fSort = dynamic_pointer_cast<FunctionSort>(_sort);
|
||||
smtAssert(fSort->codomain, "");
|
||||
smtAssert(fSort->codomain);
|
||||
string domain = toSmtLibSort(fSort->domain);
|
||||
string codomain = toSmtLibSort(*fSort->codomain);
|
||||
m_variables.insert(_name);
|
||||
|
@ -21,12 +21,31 @@
|
||||
#include <libsolutil/Assertions.h>
|
||||
#include <libsolutil/Exceptions.h>
|
||||
|
||||
#include <boost/preprocessor/cat.hpp>
|
||||
#include <boost/preprocessor/facilities/empty.hpp>
|
||||
#include <boost/preprocessor/facilities/overload.hpp>
|
||||
|
||||
namespace solidity::smtutil
|
||||
{
|
||||
|
||||
struct SMTLogicError: virtual util::Exception {};
|
||||
|
||||
#define smtAssert(CONDITION, DESCRIPTION) \
|
||||
assertThrow(CONDITION, SMTLogicError, DESCRIPTION)
|
||||
/// Assertion that throws an SMTLogicError containing the given description if it is not met.
|
||||
#if !BOOST_PP_VARIADICS_MSVC
|
||||
#define smtAssert(...) BOOST_PP_OVERLOAD(smtAssert_,__VA_ARGS__)(__VA_ARGS__)
|
||||
#else
|
||||
#define smtAssert(...) BOOST_PP_CAT(BOOST_PP_OVERLOAD(smtAssert_,__VA_ARGS__)(__VA_ARGS__),BOOST_PP_EMPTY())
|
||||
#endif
|
||||
|
||||
#define smtAssert_1(CONDITION) \
|
||||
smtAssert_2(CONDITION, "")
|
||||
|
||||
#define smtAssert_2(CONDITION, DESCRIPTION) \
|
||||
assertThrowWithDefaultDescription( \
|
||||
CONDITION, \
|
||||
::solidity::smtutil::SMTLogicError, \
|
||||
DESCRIPTION, \
|
||||
"SMT assertion failed" \
|
||||
)
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <libsmtutil/Sorts.h>
|
||||
|
||||
#include <libsolutil/Common.h>
|
||||
#include <libsolutil/Numeric.h>
|
||||
|
||||
#include <range/v3/view.hpp>
|
||||
|
||||
@ -42,11 +43,11 @@ struct SMTSolverChoice
|
||||
bool smtlib2 = false;
|
||||
bool z3 = false;
|
||||
|
||||
static constexpr SMTSolverChoice All() { return {true, true, true}; }
|
||||
static constexpr SMTSolverChoice CVC4() { return {true, false, false}; }
|
||||
static constexpr SMTSolverChoice SMTLIB2() { return {false, true, false}; }
|
||||
static constexpr SMTSolverChoice Z3() { return {false, false, true}; }
|
||||
static constexpr SMTSolverChoice None() { return {false, false, false}; }
|
||||
static constexpr SMTSolverChoice All() noexcept { return {true, true, true}; }
|
||||
static constexpr SMTSolverChoice CVC4() noexcept { return {true, false, false}; }
|
||||
static constexpr SMTSolverChoice SMTLIB2() noexcept { return {false, true, false}; }
|
||||
static constexpr SMTSolverChoice Z3() noexcept { return {false, false, true}; }
|
||||
static constexpr SMTSolverChoice None() noexcept { return {false, false, false}; }
|
||||
|
||||
static std::optional<SMTSolverChoice> fromString(std::string const& _solvers)
|
||||
{
|
||||
@ -65,7 +66,7 @@ struct SMTSolverChoice
|
||||
return solvers;
|
||||
}
|
||||
|
||||
SMTSolverChoice& operator&(SMTSolverChoice const& _other)
|
||||
SMTSolverChoice& operator&=(SMTSolverChoice const& _other)
|
||||
{
|
||||
cvc4 &= _other.cvc4;
|
||||
smtlib2 &= _other.smtlib2;
|
||||
@ -73,9 +74,10 @@ struct SMTSolverChoice
|
||||
return *this;
|
||||
}
|
||||
|
||||
SMTSolverChoice& operator&=(SMTSolverChoice const& _other)
|
||||
SMTSolverChoice operator&(SMTSolverChoice _other) const noexcept
|
||||
{
|
||||
return *this & _other;
|
||||
_other &= *this;
|
||||
return _other;
|
||||
}
|
||||
|
||||
bool operator!=(SMTSolverChoice const& _other) const noexcept { return !(*this == _other); }
|
||||
@ -101,9 +103,9 @@ struct SMTSolverChoice
|
||||
return true;
|
||||
}
|
||||
|
||||
bool none() { return !some(); }
|
||||
bool some() { return cvc4 || smtlib2 || z3; }
|
||||
bool all() { return cvc4 && smtlib2 && z3; }
|
||||
bool none() const noexcept { return !some(); }
|
||||
bool some() const noexcept { return cvc4 || smtlib2 || z3; }
|
||||
bool all() const noexcept { return cvc4 && smtlib2 && z3; }
|
||||
};
|
||||
|
||||
enum class CheckResult
|
||||
|
@ -27,6 +27,8 @@
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::frontend;
|
||||
|
@ -71,7 +71,7 @@ bool ContractLevelChecker::check(SourceUnit const& _sourceUnit)
|
||||
findDuplicateDefinitions(
|
||||
filterDeclarations<EventDefinition>(*_sourceUnit.annotation().exportedSymbols)
|
||||
);
|
||||
if (!Error::containsOnlyWarnings(m_errorReporter.errors()))
|
||||
if (Error::containsErrors(m_errorReporter.errors()))
|
||||
noErrors = false;
|
||||
for (ASTPointer<ASTNode> const& node: _sourceUnit.nodes())
|
||||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
|
||||
@ -97,7 +97,7 @@ bool ContractLevelChecker::check(ContractDefinition const& _contract)
|
||||
checkPayableFallbackWithoutReceive(_contract);
|
||||
checkStorageSize(_contract);
|
||||
|
||||
return Error::containsOnlyWarnings(m_errorReporter.errors());
|
||||
return !Error::containsErrors(m_errorReporter.errors());
|
||||
}
|
||||
|
||||
void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _contract)
|
||||
|
@ -36,7 +36,7 @@ bool ControlFlowAnalyzer::run()
|
||||
for (auto& [pair, flow]: m_cfg.allFunctionFlows())
|
||||
analyze(*pair.function, pair.contract, *flow);
|
||||
|
||||
return Error::containsOnlyWarnings(m_errorReporter.errors());
|
||||
return !Error::containsErrors(m_errorReporter.errors());
|
||||
}
|
||||
|
||||
void ControlFlowAnalyzer::analyze(FunctionDefinition const& _function, ContractDefinition const* _contract, FunctionFlow const& _flow)
|
||||
|
@ -409,7 +409,8 @@ bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly)
|
||||
void ControlFlowBuilder::visit(yul::Statement const& _statement)
|
||||
{
|
||||
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||
m_currentNode->location = langutil::SourceLocation::smallestCovering(m_currentNode->location, locationOf(_statement));
|
||||
solAssert(nativeLocationOf(_statement) == originLocationOf(_statement), "");
|
||||
m_currentNode->location = langutil::SourceLocation::smallestCovering(m_currentNode->location, nativeLocationOf(_statement));
|
||||
ASTWalker::visit(_statement);
|
||||
}
|
||||
|
||||
@ -496,12 +497,13 @@ void ControlFlowBuilder::operator()(yul::Identifier const& _identifier)
|
||||
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||
auto const& externalReferences = m_inlineAssembly->annotation().externalReferences;
|
||||
if (externalReferences.count(&_identifier))
|
||||
{
|
||||
if (auto const* declaration = dynamic_cast<VariableDeclaration const*>(externalReferences.at(&_identifier).declaration))
|
||||
{
|
||||
solAssert(nativeLocationOf(_identifier) == originLocationOf(_identifier), "");
|
||||
m_currentNode->variableOccurrences.emplace_back(
|
||||
*declaration,
|
||||
VariableOccurrence::Kind::Access,
|
||||
_identifier.debugData->location
|
||||
nativeLocationOf(_identifier)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -514,11 +516,14 @@ void ControlFlowBuilder::operator()(yul::Assignment const& _assignment)
|
||||
for (auto const& variable: _assignment.variableNames)
|
||||
if (externalReferences.count(&variable))
|
||||
if (auto const* declaration = dynamic_cast<VariableDeclaration const*>(externalReferences.at(&variable).declaration))
|
||||
{
|
||||
solAssert(nativeLocationOf(variable) == originLocationOf(variable), "");
|
||||
m_currentNode->variableOccurrences.emplace_back(
|
||||
*declaration,
|
||||
VariableOccurrence::Kind::Assignment,
|
||||
variable.debugData->location
|
||||
nativeLocationOf(variable)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void ControlFlowBuilder::operator()(yul::FunctionCall const& _functionCall)
|
||||
@ -548,7 +553,7 @@ void ControlFlowBuilder::operator()(yul::FunctionDefinition const&)
|
||||
void ControlFlowBuilder::operator()(yul::Leave const&)
|
||||
{
|
||||
// This has to be implemented, if we ever decide to visit functions.
|
||||
solUnimplementedAssert(false, "");
|
||||
solUnimplemented("");
|
||||
}
|
||||
|
||||
bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
|
||||
|
@ -27,7 +27,7 @@ using namespace solidity::frontend;
|
||||
bool CFG::constructFlow(ASTNode const& _astRoot)
|
||||
{
|
||||
_astRoot.accept(*this);
|
||||
return Error::containsOnlyWarnings(m_errorReporter.errors());
|
||||
return !Error::containsErrors(m_errorReporter.errors());
|
||||
}
|
||||
|
||||
|
||||
|
@ -140,6 +140,30 @@ bool DeclarationTypeChecker::visit(StructDefinition const& _struct)
|
||||
return false;
|
||||
}
|
||||
|
||||
void DeclarationTypeChecker::endVisit(UserDefinedValueTypeDefinition const& _userDefined)
|
||||
{
|
||||
TypeName const* typeName = _userDefined.underlyingType();
|
||||
solAssert(typeName, "");
|
||||
if (!dynamic_cast<ElementaryTypeName const*>(typeName))
|
||||
m_errorReporter.fatalTypeError(
|
||||
8657_error,
|
||||
typeName->location(),
|
||||
"The underlying type for a user defined value type has to be an elementary value type."
|
||||
);
|
||||
|
||||
Type const* type = typeName->annotation().type;
|
||||
solAssert(type, "");
|
||||
solAssert(!dynamic_cast<UserDefinedValueType const*>(type), "");
|
||||
if (!type->isValueType())
|
||||
m_errorReporter.typeError(
|
||||
8129_error,
|
||||
_userDefined.location(),
|
||||
"The underlying type of the user defined value type \"" +
|
||||
_userDefined.name() +
|
||||
"\" is not a value type."
|
||||
);
|
||||
}
|
||||
|
||||
void DeclarationTypeChecker::endVisit(UserDefinedTypeName const& _typeName)
|
||||
{
|
||||
if (_typeName.annotation().type)
|
||||
@ -158,6 +182,8 @@ void DeclarationTypeChecker::endVisit(UserDefinedTypeName const& _typeName)
|
||||
_typeName.annotation().type = TypeProvider::enumType(*enumDef);
|
||||
else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration))
|
||||
_typeName.annotation().type = TypeProvider::contract(*contract);
|
||||
else if (auto userDefinedValueType = dynamic_cast<UserDefinedValueTypeDefinition const*>(declaration))
|
||||
_typeName.annotation().type = TypeProvider::userDefinedValueType(*userDefinedValueType);
|
||||
else
|
||||
{
|
||||
_typeName.annotation().type = TypeProvider::emptyTuple();
|
||||
@ -227,12 +253,13 @@ void DeclarationTypeChecker::endVisit(Mapping const& _mapping)
|
||||
{
|
||||
case Type::Category::Enum:
|
||||
case Type::Category::Contract:
|
||||
case Type::Category::UserDefinedValueType:
|
||||
break;
|
||||
default:
|
||||
m_errorReporter.fatalTypeError(
|
||||
7804_error,
|
||||
typeName->location(),
|
||||
"Only elementary types, contract types or enums are allowed as mapping keys."
|
||||
"Only elementary types, user defined value types, contract types or enums are allowed as mapping keys."
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ private:
|
||||
void endVisit(VariableDeclaration const& _variable) override;
|
||||
bool visit(EnumDefinition const& _enum) override;
|
||||
bool visit(StructDefinition const& _struct) override;
|
||||
void endVisit(UserDefinedValueTypeDefinition const& _userDefined) override;
|
||||
bool visit(UsingForDirective const& _usingForDirective) override;
|
||||
bool visit(InheritanceSpecifier const& _inheritanceSpecifier) override;
|
||||
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <regex>
|
||||
#include <string_view>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
@ -233,7 +234,7 @@ void DocStringTagParser::parseDocStrings(
|
||||
|
||||
for (auto const& [tagName, tagValue]: _annotation.docTags)
|
||||
{
|
||||
string static const customPrefix("custom:");
|
||||
string_view static constexpr customPrefix("custom:");
|
||||
if (tagName == "custom" || tagName == "custom:")
|
||||
m_errorReporter.docstringParsingError(
|
||||
6564_error,
|
||||
|
@ -102,7 +102,12 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
|
||||
else
|
||||
for (Declaration const* declaration: declarations)
|
||||
if (!DeclarationRegistrationHelper::registerDeclaration(
|
||||
target, *declaration, alias.alias.get(), &alias.location, false, m_errorReporter
|
||||
target,
|
||||
*declaration,
|
||||
alias.alias ? alias.alias.get() : &alias.symbol->name(),
|
||||
&alias.location,
|
||||
false,
|
||||
m_errorReporter
|
||||
))
|
||||
error = true;
|
||||
}
|
||||
@ -607,13 +612,31 @@ bool DeclarationRegistrationHelper::visitNode(ASTNode& _node)
|
||||
|
||||
if (auto* declaration = dynamic_cast<Declaration*>(&_node))
|
||||
registerDeclaration(*declaration);
|
||||
|
||||
if (auto* annotation = dynamic_cast<TypeDeclarationAnnotation*>(&_node.annotation()))
|
||||
{
|
||||
string canonicalName = dynamic_cast<Declaration const&>(_node).name();
|
||||
solAssert(!canonicalName.empty(), "");
|
||||
|
||||
for (
|
||||
ASTNode const* scope = m_currentScope;
|
||||
scope != nullptr;
|
||||
scope = m_scopes[scope]->enclosingNode()
|
||||
)
|
||||
if (auto decl = dynamic_cast<Declaration const*>(scope))
|
||||
{
|
||||
solAssert(!decl->name().empty(), "");
|
||||
canonicalName = decl->name() + "." + canonicalName;
|
||||
}
|
||||
|
||||
annotation->canonicalName = canonicalName;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@ -663,23 +686,4 @@ void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaratio
|
||||
solAssert(_declaration.annotation().contract == m_currentContract, "");
|
||||
}
|
||||
|
||||
string DeclarationRegistrationHelper::currentCanonicalName() const
|
||||
{
|
||||
string ret;
|
||||
for (
|
||||
ASTNode const* scope = m_currentScope;
|
||||
scope != nullptr;
|
||||
scope = m_scopes[scope]->enclosingNode()
|
||||
)
|
||||
{
|
||||
if (auto decl = dynamic_cast<Declaration const*>(scope))
|
||||
{
|
||||
if (!ret.empty())
|
||||
ret = "." + ret;
|
||||
ret = decl->name() + ret;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -176,9 +176,6 @@ private:
|
||||
|
||||
static bool isOverloadedFunction(Declaration const& _declaration1, Declaration const& _declaration2);
|
||||
|
||||
/// @returns the canonical name of the current scope.
|
||||
std::string currentCanonicalName() const;
|
||||
|
||||
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& m_scopes;
|
||||
ASTNode const* m_currentScope = nullptr;
|
||||
VariableScope* m_currentFunction = nullptr;
|
||||
|
@ -86,7 +86,8 @@ private:
|
||||
int currentNode = static_cast<int>(numNodes++);
|
||||
nodes[_function] = currentNode;
|
||||
nodeInv[currentNode] = _function;
|
||||
if (_function.overrides())
|
||||
|
||||
if (!_function.baseFunctions().empty())
|
||||
for (auto const& baseFunction: _function.baseFunctions())
|
||||
addEdge(currentNode, visit(baseFunction));
|
||||
else
|
||||
@ -518,7 +519,7 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
|
||||
"Override changes modifier signature."
|
||||
);
|
||||
|
||||
if (!_overriding.overrides())
|
||||
if (!_overriding.overrides() && !(_super.isFunction() && _super.contract().isInterface()))
|
||||
overrideError(
|
||||
_overriding,
|
||||
_super,
|
||||
|
@ -36,14 +36,14 @@ using namespace solidity::frontend;
|
||||
bool PostTypeChecker::check(ASTNode const& _astRoot)
|
||||
{
|
||||
_astRoot.accept(*this);
|
||||
return Error::containsOnlyWarnings(m_errorReporter.errors());
|
||||
return !Error::containsErrors(m_errorReporter.errors());
|
||||
}
|
||||
|
||||
bool PostTypeChecker::finalize()
|
||||
{
|
||||
for (auto& checker: m_checkers)
|
||||
checker->finalize();
|
||||
return Error::containsOnlyWarnings(m_errorReporter.errors());
|
||||
return !Error::containsErrors(m_errorReporter.errors());
|
||||
}
|
||||
|
||||
bool PostTypeChecker::visit(ContractDefinition const& _contractDefinition)
|
||||
|
@ -68,5 +68,5 @@ bool PostTypeContractLevelChecker::check(ContractDefinition const& _contract)
|
||||
errorHashes[hash][signature] = error->location();
|
||||
}
|
||||
|
||||
return Error::containsOnlyWarnings(m_errorReporter.errors());
|
||||
return !Error::containsErrors(m_errorReporter.errors());
|
||||
}
|
||||
|
@ -201,9 +201,13 @@ bool ReferencesResolver::visit(Return const& _return)
|
||||
|
||||
void ReferencesResolver::operator()(yul::FunctionDefinition const& _function)
|
||||
{
|
||||
validateYulIdentifierName(_function.name, _function.debugData->location);
|
||||
solAssert(nativeLocationOf(_function) == originLocationOf(_function), "");
|
||||
validateYulIdentifierName(_function.name, nativeLocationOf(_function));
|
||||
for (yul::TypedName const& varName: _function.parameters + _function.returnVariables)
|
||||
validateYulIdentifierName(varName.name, varName.debugData->location);
|
||||
{
|
||||
solAssert(nativeLocationOf(varName) == originLocationOf(varName), "");
|
||||
validateYulIdentifierName(varName.name, nativeLocationOf(varName));
|
||||
}
|
||||
|
||||
bool wasInsideFunction = m_yulInsideFunction;
|
||||
m_yulInsideFunction = true;
|
||||
@ -213,13 +217,17 @@ void ReferencesResolver::operator()(yul::FunctionDefinition const& _function)
|
||||
|
||||
void ReferencesResolver::operator()(yul::Identifier const& _identifier)
|
||||
{
|
||||
static set<string> suffixes{"slot", "offset", "length"};
|
||||
solAssert(nativeLocationOf(_identifier) == originLocationOf(_identifier), "");
|
||||
|
||||
static set<string> suffixes{"slot", "offset", "length", "address", "selector"};
|
||||
string suffix;
|
||||
for (string const& s: suffixes)
|
||||
if (boost::algorithm::ends_with(_identifier.name.str(), "." + s))
|
||||
suffix = s;
|
||||
|
||||
// Could also use `pathFromCurrentScope`, split by '.'
|
||||
// Could also use `pathFromCurrentScope`, split by '.'.
|
||||
// If we do that, suffix should only be set for when it has a special
|
||||
// meaning, not for normal identifierPaths.
|
||||
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str());
|
||||
if (!suffix.empty())
|
||||
{
|
||||
@ -238,7 +246,7 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
|
||||
{
|
||||
m_errorReporter.declarationError(
|
||||
4718_error,
|
||||
_identifier.debugData->location,
|
||||
nativeLocationOf(_identifier),
|
||||
"Multiple matching identifiers. Resolving overloaded identifiers is not supported."
|
||||
);
|
||||
return;
|
||||
@ -251,7 +259,7 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
|
||||
)
|
||||
m_errorReporter.declarationError(
|
||||
9467_error,
|
||||
_identifier.debugData->location,
|
||||
nativeLocationOf(_identifier),
|
||||
"Identifier not found. Use \".slot\" and \".offset\" to access storage variables."
|
||||
);
|
||||
return;
|
||||
@ -261,7 +269,7 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
|
||||
{
|
||||
m_errorReporter.declarationError(
|
||||
6578_error,
|
||||
_identifier.debugData->location,
|
||||
nativeLocationOf(_identifier),
|
||||
"Cannot access local Solidity variables from inside an inline assembly function."
|
||||
);
|
||||
return;
|
||||
@ -274,7 +282,10 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
|
||||
void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl)
|
||||
{
|
||||
for (auto const& identifier: _varDecl.variables)
|
||||
validateYulIdentifierName(identifier.name, identifier.debugData->location);
|
||||
{
|
||||
solAssert(nativeLocationOf(identifier) == originLocationOf(identifier), "");
|
||||
validateYulIdentifierName(identifier.name, nativeLocationOf(identifier));
|
||||
}
|
||||
|
||||
if (_varDecl.value)
|
||||
visit(*_varDecl.value);
|
||||
|
@ -86,7 +86,7 @@ StaticAnalyzer::~StaticAnalyzer()
|
||||
bool StaticAnalyzer::analyze(SourceUnit const& _sourceUnit)
|
||||
{
|
||||
_sourceUnit.accept(*this);
|
||||
return Error::containsOnlyWarnings(m_errorReporter.errors());
|
||||
return !Error::containsErrors(m_errorReporter.errors());
|
||||
}
|
||||
|
||||
bool StaticAnalyzer::visit(ContractDefinition const& _contract)
|
||||
|
@ -41,7 +41,7 @@ using namespace solidity::util;
|
||||
bool SyntaxChecker::checkSyntax(ASTNode const& _astRoot)
|
||||
{
|
||||
_astRoot.accept(*this);
|
||||
return Error::containsOnlyWarnings(m_errorReporter.errors());
|
||||
return !Error::containsErrors(m_errorReporter.errors());
|
||||
}
|
||||
|
||||
bool SyntaxChecker::visit(SourceUnit const& _sourceUnit)
|
||||
|
@ -73,7 +73,7 @@ bool TypeChecker::checkTypeRequirements(SourceUnit const& _source)
|
||||
m_currentSourceUnit = &_source;
|
||||
_source.accept(*this);
|
||||
m_currentSourceUnit = nullptr;
|
||||
return Error::containsOnlyWarnings(m_errorReporter.errors());
|
||||
return !Error::containsErrors(m_errorReporter.errors());
|
||||
}
|
||||
|
||||
Type const* TypeChecker::type(Expression const& _expression) const
|
||||
@ -246,7 +246,10 @@ TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(Functio
|
||||
Type::Category typeCategory = typeTypePtr->actualType()->category();
|
||||
if (auto const* contractType = dynamic_cast<ContractType const*>(typeTypePtr->actualType()))
|
||||
wrongType = contractType->isSuper();
|
||||
else if (typeCategory != Type::Category::Integer)
|
||||
else if (
|
||||
typeCategory != Type::Category::Integer &&
|
||||
typeCategory != Type::Category::Enum
|
||||
)
|
||||
wrongType = true;
|
||||
}
|
||||
else
|
||||
@ -257,7 +260,7 @@ TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(Functio
|
||||
4259_error,
|
||||
arguments.front()->location(),
|
||||
"Invalid type for argument in the function call. "
|
||||
"A contract type or an integer type is required, but " +
|
||||
"An enum type, contract type or an integer type is required, but " +
|
||||
type(*arguments.front())->toString(true) + " provided."
|
||||
);
|
||||
|
||||
@ -314,15 +317,23 @@ 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())
|
||||
{
|
||||
if (_modifier.virtualSemantics() && contractDef->isLibrary())
|
||||
m_errorReporter.typeError(
|
||||
3275_error,
|
||||
_modifier.location(),
|
||||
"Modifiers in a library cannot be virtual."
|
||||
);
|
||||
|
||||
if (contractDef->isInterface())
|
||||
m_errorReporter.typeError(
|
||||
6408_error,
|
||||
_modifier.location(),
|
||||
"Modifiers cannot be defined or declared in interfaces."
|
||||
);
|
||||
}
|
||||
|
||||
if (!_modifier.isImplemented() && !_modifier.virtualSemantics())
|
||||
m_errorReporter.typeError(8063_error, _modifier.location(), "Modifiers without implementation must be marked virtual.");
|
||||
}
|
||||
@ -754,7 +765,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
solAssert(var->type(), "Expected variable type!");
|
||||
if (var->immutable())
|
||||
{
|
||||
m_errorReporter.typeError(3773_error, _identifier.debugData->location, "Assembly access to immutable variables is not supported.");
|
||||
m_errorReporter.typeError(3773_error, nativeLocationOf(_identifier), "Assembly access to immutable variables is not supported.");
|
||||
return false;
|
||||
}
|
||||
if (var->isConstant())
|
||||
@ -763,7 +774,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
{
|
||||
m_errorReporter.typeError(
|
||||
3558_error,
|
||||
_identifier.debugData->location,
|
||||
nativeLocationOf(_identifier),
|
||||
"Constant variable is circular."
|
||||
);
|
||||
return false;
|
||||
@ -773,24 +784,24 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
|
||||
if (var && !var->value())
|
||||
{
|
||||
m_errorReporter.typeError(3224_error, _identifier.debugData->location, "Constant has no value.");
|
||||
m_errorReporter.typeError(3224_error, nativeLocationOf(_identifier), "Constant has no value.");
|
||||
return false;
|
||||
}
|
||||
else if (_context == yul::IdentifierContext::LValue)
|
||||
{
|
||||
m_errorReporter.typeError(6252_error, _identifier.debugData->location, "Constant variables cannot be assigned to.");
|
||||
m_errorReporter.typeError(6252_error, nativeLocationOf(_identifier), "Constant variables cannot be assigned to.");
|
||||
return false;
|
||||
}
|
||||
else if (!identifierInfo.suffix.empty())
|
||||
else if (identifierInfo.suffix == "slot" || identifierInfo.suffix == "offset")
|
||||
{
|
||||
m_errorReporter.typeError(6617_error, _identifier.debugData->location, "The suffixes .offset and .slot can only be used on non-constant storage variables.");
|
||||
m_errorReporter.typeError(6617_error, nativeLocationOf(_identifier), "The suffixes .offset and .slot can only be used on non-constant storage variables.");
|
||||
return false;
|
||||
}
|
||||
else if (var && var->value() && !var->value()->annotation().type && !dynamic_cast<Literal const*>(var->value().get()))
|
||||
{
|
||||
m_errorReporter.typeError(
|
||||
2249_error,
|
||||
_identifier.debugData->location,
|
||||
nativeLocationOf(_identifier),
|
||||
"Constant variables with non-literal values cannot be forward referenced from inline assembly."
|
||||
);
|
||||
return false;
|
||||
@ -800,7 +811,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
type(*var->value())->category() != Type::Category::RationalNumber
|
||||
))
|
||||
{
|
||||
m_errorReporter.typeError(7615_error, _identifier.debugData->location, "Only direct number constants and references to such constants are supported by inline assembly.");
|
||||
m_errorReporter.typeError(7615_error, nativeLocationOf(_identifier), "Only direct number constants and references to such constants are supported by inline assembly.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -810,24 +821,24 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
if (!identifierInfo.suffix.empty())
|
||||
{
|
||||
string const& suffix = identifierInfo.suffix;
|
||||
solAssert((set<string>{"offset", "slot", "length"}).count(suffix), "");
|
||||
if (var->isStateVariable() || var->type()->dataStoredIn(DataLocation::Storage))
|
||||
solAssert((set<string>{"offset", "slot", "length", "selector", "address"}).count(suffix), "");
|
||||
if (!var->isConstant() && (var->isStateVariable() || var->type()->dataStoredIn(DataLocation::Storage)))
|
||||
{
|
||||
if (suffix != "slot" && suffix != "offset")
|
||||
{
|
||||
m_errorReporter.typeError(4656_error, _identifier.debugData->location, "State variables only support \".slot\" and \".offset\".");
|
||||
m_errorReporter.typeError(4656_error, nativeLocationOf(_identifier), "State variables only support \".slot\" and \".offset\".");
|
||||
return false;
|
||||
}
|
||||
else if (_context == yul::IdentifierContext::LValue)
|
||||
{
|
||||
if (var->isStateVariable())
|
||||
{
|
||||
m_errorReporter.typeError(4713_error, _identifier.debugData->location, "State variables cannot be assigned to - you have to use \"sstore()\".");
|
||||
m_errorReporter.typeError(4713_error, nativeLocationOf(_identifier), "State variables cannot be assigned to - you have to use \"sstore()\".");
|
||||
return false;
|
||||
}
|
||||
else if (suffix != "slot")
|
||||
{
|
||||
m_errorReporter.typeError(9739_error, _identifier.debugData->location, "Only .slot can be assigned to.");
|
||||
m_errorReporter.typeError(9739_error, nativeLocationOf(_identifier), "Only .slot can be assigned to.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -839,13 +850,26 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
{
|
||||
if (suffix != "offset" && suffix != "length")
|
||||
{
|
||||
m_errorReporter.typeError(1536_error, _identifier.debugData->location, "Calldata variables only support \".offset\" and \".length\".");
|
||||
m_errorReporter.typeError(1536_error, nativeLocationOf(_identifier), "Calldata variables only support \".offset\" and \".length\".");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (auto const* fpType = dynamic_cast<FunctionTypePointer>(var->type()))
|
||||
{
|
||||
if (suffix != "selector" && suffix != "address")
|
||||
{
|
||||
m_errorReporter.typeError(9272_error, nativeLocationOf(_identifier), "Variables of type function pointer only support \".selector\" and \".address\".");
|
||||
return false;
|
||||
}
|
||||
if (fpType->kind() != FunctionType::Kind::External)
|
||||
{
|
||||
m_errorReporter.typeError(8533_error, nativeLocationOf(_identifier), "Only Variables of type external function pointer support \".selector\" and \".address\".");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_errorReporter.typeError(3622_error, _identifier.debugData->location, "The suffix \"." + suffix + "\" is not supported by this variable or type.");
|
||||
m_errorReporter.typeError(3622_error, nativeLocationOf(_identifier), "The suffix \"." + suffix + "\" is not supported by this variable or type.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -853,14 +877,14 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
{
|
||||
m_errorReporter.typeError(
|
||||
1408_error,
|
||||
_identifier.debugData->location,
|
||||
nativeLocationOf(_identifier),
|
||||
"Only local variables are supported. To access storage variables, use the \".slot\" and \".offset\" suffixes."
|
||||
);
|
||||
return false;
|
||||
}
|
||||
else if (var->type()->dataStoredIn(DataLocation::Storage))
|
||||
{
|
||||
m_errorReporter.typeError(9068_error, _identifier.debugData->location, "You have to use the \".slot\" or \".offset\" suffix to access storage reference variables.");
|
||||
m_errorReporter.typeError(9068_error, nativeLocationOf(_identifier), "You have to use the \".slot\" or \".offset\" suffix to access storage reference variables.");
|
||||
return false;
|
||||
}
|
||||
else if (var->type()->sizeOnStack() != 1)
|
||||
@ -869,18 +893,18 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
auto const* arrayType = dynamic_cast<ArrayType const*>(var->type());
|
||||
arrayType && arrayType->isDynamicallySized() && arrayType->dataStoredIn(DataLocation::CallData)
|
||||
)
|
||||
m_errorReporter.typeError(1397_error, _identifier.debugData->location, "Call data elements cannot be accessed directly. Use \".offset\" and \".length\" to access the calldata offset and length of this array and then use \"calldatacopy\".");
|
||||
m_errorReporter.typeError(1397_error, nativeLocationOf(_identifier), "Call data elements cannot be accessed directly. Use \".offset\" and \".length\" to access the calldata offset and length of this array and then use \"calldatacopy\".");
|
||||
else
|
||||
{
|
||||
solAssert(!var->type()->dataStoredIn(DataLocation::CallData), "");
|
||||
m_errorReporter.typeError(9857_error, _identifier.debugData->location, "Only types that use one stack slot are supported.");
|
||||
m_errorReporter.typeError(9857_error, nativeLocationOf(_identifier), "Only types that use one stack slot are supported.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!identifierInfo.suffix.empty())
|
||||
{
|
||||
m_errorReporter.typeError(7944_error, _identifier.debugData->location, "The suffixes \".offset\", \".slot\" and \".length\" can only be used with variables.");
|
||||
m_errorReporter.typeError(7944_error, nativeLocationOf(_identifier), "The suffixes \".offset\", \".slot\" and \".length\" can only be used with variables.");
|
||||
return false;
|
||||
}
|
||||
else if (_context == yul::IdentifierContext::LValue)
|
||||
@ -888,7 +912,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
if (dynamic_cast<MagicVariableDeclaration const*>(declaration))
|
||||
return false;
|
||||
|
||||
m_errorReporter.typeError(1990_error, _identifier.debugData->location, "Only local variables can be assigned to in inline assembly.");
|
||||
m_errorReporter.typeError(1990_error, nativeLocationOf(_identifier), "Only local variables can be assigned to in inline assembly.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -897,7 +921,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
solAssert(!!declaration->type(), "Type of declaration required but not yet determined.");
|
||||
if (dynamic_cast<FunctionDefinition const*>(declaration))
|
||||
{
|
||||
m_errorReporter.declarationError(2025_error, _identifier.debugData->location, "Access to functions is not allowed in inline assembly.");
|
||||
m_errorReporter.declarationError(2025_error, nativeLocationOf(_identifier), "Access to functions is not allowed in inline assembly.");
|
||||
return false;
|
||||
}
|
||||
else if (dynamic_cast<VariableDeclaration const*>(declaration))
|
||||
@ -907,7 +931,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
{
|
||||
if (!contract->isLibrary())
|
||||
{
|
||||
m_errorReporter.typeError(4977_error, _identifier.debugData->location, "Expected a library.");
|
||||
m_errorReporter.typeError(4977_error, nativeLocationOf(_identifier), "Expected a library.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -2470,6 +2494,13 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
returnTypes = functionType->returnParameterTypes();
|
||||
break;
|
||||
}
|
||||
case FunctionType::Kind::Wrap:
|
||||
case FunctionType::Kind::Unwrap:
|
||||
{
|
||||
typeCheckFunctionGeneralChecks(_functionCall, functionType);
|
||||
returnTypes = functionType->returnParameterTypes();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
typeCheckFunctionCall(_functionCall, functionType);
|
||||
@ -2907,7 +2938,10 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
// 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.
|
||||
if (auto tt = dynamic_cast<TypeType const*>(exprType))
|
||||
if (tt->actualType()->category() == Type::Category::Enum)
|
||||
if (
|
||||
tt->actualType()->category() == Type::Category::Enum ||
|
||||
tt->actualType()->category() == Type::Category::UserDefinedValueType
|
||||
)
|
||||
annotation.isPure = true;
|
||||
if (
|
||||
auto const* functionType = dynamic_cast<FunctionType const*>(exprType);
|
||||
|
@ -69,7 +69,7 @@ public:
|
||||
if (yul::EVMDialect const* dialect = dynamic_cast<decltype(dialect)>(&m_dialect))
|
||||
if (yul::BuiltinFunctionForEVM const* fun = dialect->builtin(_funCall.functionName.name))
|
||||
if (fun->instruction)
|
||||
checkInstruction(_funCall.debugData->location, *fun->instruction);
|
||||
checkInstruction(nativeLocationOf(_funCall), *fun->instruction);
|
||||
|
||||
for (auto const& arg: _funCall.arguments)
|
||||
std::visit(*this, arg);
|
||||
|
@ -274,7 +274,7 @@ uint32_t ContractDefinition::interfaceId() const
|
||||
{
|
||||
uint32_t result{0};
|
||||
for (auto const& function: interfaceFunctionList(false))
|
||||
result ^= util::fromBigEndian<uint32_t>(function.first.ref());
|
||||
result ^= fromBigEndian<uint32_t>(function.first.ref());
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -335,6 +335,17 @@ TypeNameAnnotation& TypeName::annotation() const
|
||||
return initAnnotation<TypeNameAnnotation>();
|
||||
}
|
||||
|
||||
Type const* UserDefinedValueTypeDefinition::type() const
|
||||
{
|
||||
solAssert(m_underlyingType->annotation().type, "");
|
||||
return TypeProvider::typeType(TypeProvider::userDefinedValueType(*this));
|
||||
}
|
||||
|
||||
TypeDeclarationAnnotation& UserDefinedValueTypeDefinition::annotation() const
|
||||
{
|
||||
return initAnnotation<TypeDeclarationAnnotation>();
|
||||
}
|
||||
|
||||
Type const* StructDefinition::type() const
|
||||
{
|
||||
solAssert(annotation().recursive.has_value(), "Requested struct type before DeclarationTypeChecker.");
|
||||
|
@ -726,6 +726,40 @@ public:
|
||||
Type const* type() const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* User defined value types, i.e., custom types, for example, `type MyInt is int`. Allows creating a
|
||||
* zero cost abstraction over value type with stricter type requirements.
|
||||
*/
|
||||
class UserDefinedValueTypeDefinition: public Declaration
|
||||
{
|
||||
public:
|
||||
UserDefinedValueTypeDefinition(
|
||||
int64_t _id,
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> _name,
|
||||
SourceLocation _nameLocation,
|
||||
ASTPointer<TypeName> _underlyingType
|
||||
):
|
||||
Declaration(_id, _location, _name, std::move(_nameLocation), Visibility::Default),
|
||||
m_underlyingType(std::move(_underlyingType))
|
||||
{
|
||||
}
|
||||
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
void accept(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
Type const* type() const override;
|
||||
|
||||
TypeDeclarationAnnotation& annotation() const override;
|
||||
|
||||
TypeName const* underlyingType() const { return m_underlyingType.get(); }
|
||||
bool isVisibleViaContractTypeAccess() const override { return true; }
|
||||
|
||||
private:
|
||||
/// The name of the underlying type
|
||||
ASTPointer<TypeName> m_underlyingType;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parameter list, used as function parameter list, return list and for try and catch.
|
||||
* None of the parameters is allowed to contain mappings (not even recursively
|
||||
|
@ -211,7 +211,7 @@ struct InlineAssemblyAnnotation: StatementAnnotation
|
||||
struct ExternalIdentifierInfo
|
||||
{
|
||||
Declaration const* declaration = nullptr;
|
||||
/// Suffix used, one of "slot", "offset", "length" or empty.
|
||||
/// Suffix used, one of "slot", "offset", "length", "address", "selector" or empty.
|
||||
std::string suffix;
|
||||
size_t valueSize = size_t(-1);
|
||||
};
|
||||
|
@ -51,6 +51,7 @@ class UsingForDirective;
|
||||
class StructDefinition;
|
||||
class EnumDefinition;
|
||||
class EnumValue;
|
||||
class UserDefinedValueTypeDefinition;
|
||||
class ParameterList;
|
||||
class FunctionDefinition;
|
||||
class VariableDeclaration;
|
||||
|
@ -26,12 +26,12 @@
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
|
||||
#include <libyul/AsmJsonConverter.h>
|
||||
#include <libyul/AsmPrinter.h>
|
||||
#include <libyul/AST.h>
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
|
||||
#include <libsolutil/JSON.h>
|
||||
#include <libsolutil/UTF8.h>
|
||||
#include <libsolutil/CommonData.h>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
|
||||
@ -172,16 +172,19 @@ void ASTJsonConverter::appendExpressionAttributes(
|
||||
_attributes += exprAttributes;
|
||||
}
|
||||
|
||||
Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair<yul::Identifier const* ,InlineAssemblyAnnotation::ExternalIdentifierInfo> _info) const
|
||||
Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair<yul::Identifier const*, InlineAssemblyAnnotation::ExternalIdentifierInfo> _info) const
|
||||
{
|
||||
Json::Value tuple(Json::objectValue);
|
||||
tuple["src"] = sourceLocationToString(_info.first->debugData->location);
|
||||
tuple["src"] = sourceLocationToString(nativeLocationOf(*_info.first));
|
||||
tuple["declaration"] = idOrNull(_info.second.declaration);
|
||||
tuple["isSlot"] = Json::Value(_info.second.suffix == "slot");
|
||||
tuple["isOffset"] = Json::Value(_info.second.suffix == "offset");
|
||||
|
||||
if (!_info.second.suffix.empty())
|
||||
tuple["suffix"] = Json::Value(_info.second.suffix);
|
||||
|
||||
tuple["valueSize"] = Json::Value(Json::LargestUInt(_info.second.valueSize));
|
||||
|
||||
return tuple;
|
||||
}
|
||||
|
||||
@ -276,6 +279,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
|
||||
make_pair("nodes", toJson(_node.subNodes())),
|
||||
make_pair("scope", idOrNull(_node.scope()))
|
||||
};
|
||||
addIfSet(attributes, "canonicalName", _node.annotation().canonicalName);
|
||||
|
||||
if (_node.annotation().unimplementedDeclarations.has_value())
|
||||
attributes.emplace_back("fullyImplemented", _node.annotation().unimplementedDeclarations->empty());
|
||||
@ -354,6 +358,21 @@ bool ASTJsonConverter::visit(EnumValue const& _node)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ASTJsonConverter::visit(UserDefinedValueTypeDefinition const& _node)
|
||||
{
|
||||
solAssert(_node.underlyingType(), "");
|
||||
std::vector<pair<string, Json::Value>> attributes = {
|
||||
make_pair("name", _node.name()),
|
||||
make_pair("nameLocation", sourceLocationToString(_node.nameLocation())),
|
||||
make_pair("underlyingType", toJson(*_node.underlyingType()))
|
||||
};
|
||||
addIfSet(attributes, "canonicalName", _node.annotation().canonicalName);
|
||||
|
||||
setJsonNode(_node, "UserDefinedValueTypeDefinition", std::move(attributes));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ASTJsonConverter::visit(ParameterList const& _node)
|
||||
{
|
||||
setJsonNode(_node, "ParameterList", {
|
||||
|
@ -81,6 +81,7 @@ public:
|
||||
bool visit(StructDefinition const& _node) override;
|
||||
bool visit(EnumDefinition const& _node) override;
|
||||
bool visit(EnumValue const& _node) override;
|
||||
bool visit(UserDefinedValueTypeDefinition const& _node) override;
|
||||
bool visit(ParameterList const& _node) override;
|
||||
bool visit(OverrideSpecifier const& _node) override;
|
||||
bool visit(FunctionDefinition const& _node) override;
|
||||
|
@ -61,7 +61,7 @@ map<string, ASTPointer<SourceUnit>> ASTJsonImporter::jsonToSourceUnit(map<string
|
||||
m_sourceNames.emplace_back(make_shared<string const>(src.first));
|
||||
for (auto const& srcPair: _sourceList)
|
||||
{
|
||||
astAssert(!srcPair.second.isNull(), "");
|
||||
astAssert(!srcPair.second.isNull());
|
||||
astAssert(member(srcPair.second,"nodeType") == "SourceUnit", "The 'nodeType' of the highest node must be 'SourceUnit'.");
|
||||
m_sourceUnits[srcPair.first] = createSourceUnit(srcPair.second, srcPair.first);
|
||||
}
|
||||
@ -133,6 +133,8 @@ ASTPointer<ASTNode> ASTJsonImporter::convertJsonToASTNode(Json::Value const& _js
|
||||
return createEnumDefinition(_json);
|
||||
if (nodeType == "EnumValue")
|
||||
return createEnumValue(_json);
|
||||
if (nodeType == "UserDefinedValueTypeDefinition")
|
||||
return createUserDefinedValueTypeDefinition(_json);
|
||||
if (nodeType == "ParameterList")
|
||||
return createParameterList(_json);
|
||||
if (nodeType == "OverrideSpecifier")
|
||||
@ -387,6 +389,16 @@ ASTPointer<EnumValue> ASTJsonImporter::createEnumValue(Json::Value const& _node)
|
||||
);
|
||||
}
|
||||
|
||||
ASTPointer<UserDefinedValueTypeDefinition> ASTJsonImporter::createUserDefinedValueTypeDefinition(Json::Value const& _node)
|
||||
{
|
||||
return createASTNode<UserDefinedValueTypeDefinition>(
|
||||
_node,
|
||||
memberAsASTString(_node, "name"),
|
||||
createNameSourceLocation(_node),
|
||||
convertJsonToASTNode<TypeName>(member(_node, "underlyingType"))
|
||||
);
|
||||
}
|
||||
|
||||
ASTPointer<ParameterList> ASTJsonImporter::createParameterList(Json::Value const& _node)
|
||||
{
|
||||
std::vector<ASTPointer<VariableDeclaration>> parameters;
|
||||
@ -473,17 +485,17 @@ ASTPointer<VariableDeclaration> ASTJsonImporter::createVariableDeclaration(Json:
|
||||
if (mutabilityStr == "constant")
|
||||
{
|
||||
mutability = VariableDeclaration::Mutability::Constant;
|
||||
astAssert(memberAsBool(_node, "constant"), "");
|
||||
astAssert(memberAsBool(_node, "constant"));
|
||||
}
|
||||
else
|
||||
{
|
||||
astAssert(!memberAsBool(_node, "constant"), "");
|
||||
astAssert(!memberAsBool(_node, "constant"));
|
||||
if (mutabilityStr == "mutable")
|
||||
mutability = VariableDeclaration::Mutability::Mutable;
|
||||
else if (mutabilityStr == "immutable")
|
||||
mutability = VariableDeclaration::Mutability::Immutable;
|
||||
else
|
||||
astAssert(false, "");
|
||||
astAssert(false);
|
||||
}
|
||||
|
||||
return createASTNode<VariableDeclaration>(
|
||||
|
@ -81,6 +81,7 @@ private:
|
||||
ASTPointer<ASTNode> createStructDefinition(Json::Value const& _node);
|
||||
ASTPointer<EnumDefinition> createEnumDefinition(Json::Value const& _node);
|
||||
ASTPointer<EnumValue> createEnumValue(Json::Value const& _node);
|
||||
ASTPointer<UserDefinedValueTypeDefinition> createUserDefinedValueTypeDefinition(Json::Value const& _node);
|
||||
ASTPointer<ParameterList> createParameterList(Json::Value const& _node);
|
||||
ASTPointer<OverrideSpecifier> createOverrideSpecifier(Json::Value const& _node);
|
||||
ASTPointer<FunctionDefinition> createFunctionDefinition(Json::Value const& _node);
|
||||
|
@ -61,6 +61,7 @@ public:
|
||||
virtual bool visit(IdentifierPath& _node) { return visitNode(_node); }
|
||||
virtual bool visit(InheritanceSpecifier& _node) { return visitNode(_node); }
|
||||
virtual bool visit(UsingForDirective& _node) { return visitNode(_node); }
|
||||
virtual bool visit(UserDefinedValueTypeDefinition& _node) { return visitNode(_node); }
|
||||
virtual bool visit(StructDefinition& _node) { return visitNode(_node); }
|
||||
virtual bool visit(EnumDefinition& _node) { return visitNode(_node); }
|
||||
virtual bool visit(EnumValue& _node) { return visitNode(_node); }
|
||||
@ -116,6 +117,7 @@ public:
|
||||
virtual void endVisit(IdentifierPath& _node) { endVisitNode(_node); }
|
||||
virtual void endVisit(InheritanceSpecifier& _node) { endVisitNode(_node); }
|
||||
virtual void endVisit(UsingForDirective& _node) { endVisitNode(_node); }
|
||||
virtual void endVisit(UserDefinedValueTypeDefinition& _node) { endVisitNode(_node); }
|
||||
virtual void endVisit(StructDefinition& _node) { endVisitNode(_node); }
|
||||
virtual void endVisit(EnumDefinition& _node) { endVisitNode(_node); }
|
||||
virtual void endVisit(EnumValue& _node) { endVisitNode(_node); }
|
||||
@ -194,6 +196,7 @@ public:
|
||||
virtual bool visit(InheritanceSpecifier const& _node) { return visitNode(_node); }
|
||||
virtual bool visit(StructDefinition const& _node) { return visitNode(_node); }
|
||||
virtual bool visit(UsingForDirective const& _node) { return visitNode(_node); }
|
||||
virtual bool visit(UserDefinedValueTypeDefinition const& _node) { return visitNode(_node); }
|
||||
virtual bool visit(EnumDefinition const& _node) { return visitNode(_node); }
|
||||
virtual bool visit(EnumValue const& _node) { return visitNode(_node); }
|
||||
virtual bool visit(ParameterList const& _node) { return visitNode(_node); }
|
||||
@ -248,6 +251,7 @@ public:
|
||||
virtual void endVisit(IdentifierPath const& _node) { endVisitNode(_node); }
|
||||
virtual void endVisit(InheritanceSpecifier const& _node) { endVisitNode(_node); }
|
||||
virtual void endVisit(UsingForDirective const& _node) { endVisitNode(_node); }
|
||||
virtual void endVisit(UserDefinedValueTypeDefinition const& _node) { endVisitNode(_node); }
|
||||
virtual void endVisit(StructDefinition const& _node) { endVisitNode(_node); }
|
||||
virtual void endVisit(EnumDefinition const& _node) { endVisitNode(_node); }
|
||||
virtual void endVisit(EnumValue const& _node) { endVisitNode(_node); }
|
||||
|
@ -164,6 +164,26 @@ void EnumValue::accept(ASTConstVisitor& _visitor) const
|
||||
_visitor.endVisit(*this);
|
||||
}
|
||||
|
||||
void UserDefinedValueTypeDefinition::accept(ASTConstVisitor& _visitor) const
|
||||
{
|
||||
if (_visitor.visit(*this))
|
||||
{
|
||||
if (m_underlyingType)
|
||||
m_underlyingType->accept(_visitor);
|
||||
}
|
||||
_visitor.endVisit(*this);
|
||||
}
|
||||
|
||||
void UserDefinedValueTypeDefinition::accept(ASTVisitor& _visitor)
|
||||
{
|
||||
if (_visitor.visit(*this))
|
||||
{
|
||||
if (m_underlyingType)
|
||||
m_underlyingType->accept(_visitor);
|
||||
}
|
||||
_visitor.endVisit(*this);
|
||||
}
|
||||
|
||||
void UsingForDirective::accept(ASTVisitor& _visitor)
|
||||
{
|
||||
if (_visitor.visit(*this))
|
||||
|
@ -566,9 +566,10 @@ MagicType const* TypeProvider::meta(Type const* _type)
|
||||
solAssert(
|
||||
_type && (
|
||||
_type->category() == Type::Category::Contract ||
|
||||
_type->category() == Type::Category::Integer
|
||||
_type->category() == Type::Category::Integer ||
|
||||
_type->category() == Type::Category::Enum
|
||||
),
|
||||
"Only contracts or integer types supported for now."
|
||||
"Only enum, contracts or integer types supported for now."
|
||||
);
|
||||
return createAndGet<MagicType>(_type);
|
||||
}
|
||||
@ -577,3 +578,8 @@ MappingType const* TypeProvider::mapping(Type const* _keyType, Type const* _valu
|
||||
{
|
||||
return createAndGet<MappingType>(_keyType, _valueType);
|
||||
}
|
||||
|
||||
UserDefinedValueType const* TypeProvider::userDefinedValueType(UserDefinedValueTypeDefinition const& _definition)
|
||||
{
|
||||
return createAndGet<UserDefinedValueType>(_definition);
|
||||
}
|
||||
|
@ -201,6 +201,8 @@ public:
|
||||
|
||||
static MappingType const* mapping(Type const* _keyType, Type const* _valueType);
|
||||
|
||||
static UserDefinedValueType const* userDefinedValueType(UserDefinedValueTypeDefinition const& _definition);
|
||||
|
||||
private:
|
||||
/// Global TypeProvider instance.
|
||||
static TypeProvider& instance()
|
||||
|
@ -1135,7 +1135,7 @@ IntegerType const* RationalNumberType::integerType() const
|
||||
return nullptr;
|
||||
else
|
||||
return TypeProvider::integer(
|
||||
max(util::bytesRequired(value), 1u) * 8,
|
||||
max(numberEncodingSize(value), 1u) * 8,
|
||||
negative ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned
|
||||
);
|
||||
}
|
||||
@ -1169,7 +1169,7 @@ FixedPointType const* RationalNumberType::fixedPointType() const
|
||||
if (v > u256(-1))
|
||||
return nullptr;
|
||||
|
||||
unsigned totalBits = max(util::bytesRequired(v), 1u) * 8;
|
||||
unsigned totalBits = max(numberEncodingSize(v), 1u) * 8;
|
||||
solAssert(totalBits <= 256, "");
|
||||
|
||||
return TypeProvider::fixedPoint(
|
||||
@ -2537,6 +2537,37 @@ unsigned EnumType::memberValue(ASTString const& _member) const
|
||||
solAssert(false, "Requested unknown enum value " + _member);
|
||||
}
|
||||
|
||||
Type const& UserDefinedValueType::underlyingType() const
|
||||
{
|
||||
Type const* type = m_definition.underlyingType()->annotation().type;
|
||||
solAssert(type, "");
|
||||
solAssert(type->category() != Category::UserDefinedValueType, "");
|
||||
return *type;
|
||||
}
|
||||
|
||||
string UserDefinedValueType::richIdentifier() const
|
||||
{
|
||||
return "t_userDefinedValueType" + parenthesizeIdentifier(m_definition.name()) + to_string(m_definition.id());
|
||||
}
|
||||
|
||||
bool UserDefinedValueType::operator==(Type const& _other) const
|
||||
{
|
||||
if (_other.category() != category())
|
||||
return false;
|
||||
UserDefinedValueType const& other = dynamic_cast<UserDefinedValueType const&>(_other);
|
||||
return other.definition() == definition();
|
||||
}
|
||||
|
||||
string UserDefinedValueType::toString(bool /* _short */) const
|
||||
{
|
||||
return *definition().annotation().canonicalName;
|
||||
}
|
||||
|
||||
vector<tuple<string, Type const*>> UserDefinedValueType::makeStackItems() const
|
||||
{
|
||||
return underlyingType().stackItems();
|
||||
}
|
||||
|
||||
BoolResult TupleType::isImplicitlyConvertibleTo(Type const& _other) const
|
||||
{
|
||||
if (auto tupleType = dynamic_cast<TupleType const*>(&_other))
|
||||
@ -2888,6 +2919,8 @@ string FunctionType::richIdentifier() const
|
||||
case Kind::GasLeft: id += "gasleft"; break;
|
||||
case Kind::Event: id += "event"; break;
|
||||
case Kind::Error: id += "error"; break;
|
||||
case Kind::Wrap: id += "wrap"; break;
|
||||
case Kind::Unwrap: id += "unwrap"; break;
|
||||
case Kind::SetGas: id += "setgas"; break;
|
||||
case Kind::SetValue: id += "setvalue"; break;
|
||||
case Kind::BlockHash: id += "blockhash"; break;
|
||||
@ -3043,10 +3076,7 @@ u256 FunctionType::storageSize() const
|
||||
|
||||
bool FunctionType::leftAligned() const
|
||||
{
|
||||
if (m_kind == Kind::External)
|
||||
return true;
|
||||
else
|
||||
solAssert(false, "Alignment property of non-exportable function type requested.");
|
||||
return m_kind == Kind::External;
|
||||
}
|
||||
|
||||
unsigned FunctionType::storageBytes() const
|
||||
@ -3471,7 +3501,9 @@ bool FunctionType::isPure() const
|
||||
m_kind == Kind::ABIEncodeWithSelector ||
|
||||
m_kind == Kind::ABIEncodeWithSignature ||
|
||||
m_kind == Kind::ABIDecode ||
|
||||
m_kind == Kind::MetaType;
|
||||
m_kind == Kind::MetaType ||
|
||||
m_kind == Kind::Wrap ||
|
||||
m_kind == Kind::Unwrap;
|
||||
}
|
||||
|
||||
TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)
|
||||
@ -3758,6 +3790,34 @@ MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) cons
|
||||
for (ASTPointer<EnumValue> const& enumValue: enumDef.members())
|
||||
members.emplace_back(enumValue.get(), enumType);
|
||||
}
|
||||
else if (m_actualType->category() == Category::UserDefinedValueType)
|
||||
{
|
||||
auto& userDefined = dynamic_cast<UserDefinedValueType const&>(*m_actualType);
|
||||
members.emplace_back(
|
||||
"wrap",
|
||||
TypeProvider::function(
|
||||
TypePointers{&userDefined.underlyingType()},
|
||||
TypePointers{&userDefined},
|
||||
strings{string{}},
|
||||
strings{string{}},
|
||||
FunctionType::Kind::Wrap,
|
||||
false, /*_arbitraryParameters */
|
||||
StateMutability::Pure
|
||||
)
|
||||
);
|
||||
members.emplace_back(
|
||||
"unwrap",
|
||||
TypeProvider::function(
|
||||
TypePointers{&userDefined},
|
||||
TypePointers{&userDefined.underlyingType()},
|
||||
strings{string{}},
|
||||
strings{string{}},
|
||||
FunctionType::Kind::Unwrap,
|
||||
false, /* _arbitraryParameters */
|
||||
StateMutability::Pure
|
||||
)
|
||||
);
|
||||
}
|
||||
else if (
|
||||
auto const* arrayType = dynamic_cast<ArrayType const*>(m_actualType);
|
||||
arrayType && arrayType->isByteArray()
|
||||
@ -3965,9 +4025,10 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
|
||||
solAssert(
|
||||
m_typeArgument && (
|
||||
m_typeArgument->category() == Type::Category::Contract ||
|
||||
m_typeArgument->category() == Type::Category::Integer
|
||||
m_typeArgument->category() == Type::Category::Integer ||
|
||||
m_typeArgument->category() == Type::Category::Enum
|
||||
),
|
||||
"Only contracts or integer types supported for now"
|
||||
"Only enums, contracts or integer types supported for now"
|
||||
);
|
||||
|
||||
if (m_typeArgument->category() == Type::Category::Contract)
|
||||
@ -3993,6 +4054,14 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
|
||||
{"max", integerTypePointer},
|
||||
});
|
||||
}
|
||||
else if (m_typeArgument->category() == Type::Category::Enum)
|
||||
{
|
||||
EnumType const* enumTypePointer = dynamic_cast<EnumType const*>(m_typeArgument);
|
||||
return MemberList::MemberMap({
|
||||
{"min", enumTypePointer},
|
||||
{"max", enumTypePointer},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
solAssert(false, "Unknown kind of magic.");
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <liblangutil/Exceptions.h>
|
||||
|
||||
#include <libsolutil/Common.h>
|
||||
#include <libsolutil/Numeric.h>
|
||||
#include <libsolutil/CommonIO.h>
|
||||
#include <libsolutil/LazyInit.h>
|
||||
#include <libsolutil/Result.h>
|
||||
@ -174,7 +175,7 @@ public:
|
||||
enum class Category
|
||||
{
|
||||
Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, ArraySlice,
|
||||
FixedBytes, Contract, Struct, Function, Enum, Tuple,
|
||||
FixedBytes, Contract, Struct, Function, Enum, UserDefinedValueType, Tuple,
|
||||
Mapping, TypeType, Modifier, Magic, Module,
|
||||
InaccessibleDynamic
|
||||
};
|
||||
@ -1071,11 +1072,85 @@ public:
|
||||
/// @returns the value that the string has in the Enum
|
||||
unsigned int memberValue(ASTString const& _member) const;
|
||||
size_t numberOfMembers() const;
|
||||
unsigned int minValue() const { return 0; }
|
||||
unsigned int maxValue() const
|
||||
{
|
||||
solAssert(numberOfMembers() <= 256, "");
|
||||
return static_cast<unsigned int>(numberOfMembers()) - 1;
|
||||
}
|
||||
|
||||
private:
|
||||
EnumDefinition const& m_enum;
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of a UserDefinedValueType.
|
||||
*/
|
||||
class UserDefinedValueType: public Type
|
||||
{
|
||||
public:
|
||||
explicit UserDefinedValueType(UserDefinedValueTypeDefinition const& _definition):
|
||||
m_definition(_definition)
|
||||
{}
|
||||
|
||||
Category category() const override { return Category::UserDefinedValueType; }
|
||||
Type const& underlyingType() const;
|
||||
UserDefinedValueTypeDefinition const& definition() const { return m_definition; }
|
||||
|
||||
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
|
||||
Type const* encodingType() const override { return &underlyingType(); }
|
||||
TypeResult interfaceType(bool /* _inLibrary */) const override {return &underlyingType(); }
|
||||
std::string richIdentifier() const override;
|
||||
bool operator==(Type const& _other) const override;
|
||||
|
||||
unsigned calldataEncodedSize(bool _padded) const override { return underlyingType().calldataEncodedSize(_padded); }
|
||||
|
||||
bool leftAligned() const override { return underlyingType().leftAligned(); }
|
||||
bool canBeStored() const override { return underlyingType().canBeStored(); }
|
||||
u256 storageSize() const override { return underlyingType().storageSize(); }
|
||||
unsigned storageBytes() const override { return underlyingType().storageBytes(); }
|
||||
|
||||
bool isValueType() const override
|
||||
{
|
||||
solAssert(underlyingType().isValueType(), "");
|
||||
return true;
|
||||
}
|
||||
bool nameable() const override
|
||||
{
|
||||
solAssert(underlyingType().nameable(), "");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool containsNestedMapping() const override
|
||||
{
|
||||
solAssert(nameable(), "Called for a non nameable type.");
|
||||
solAssert(!underlyingType().containsNestedMapping(), "");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasSimpleZeroValueInMemory() const override
|
||||
{
|
||||
solAssert(underlyingType().hasSimpleZeroValueInMemory(), "");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dataStoredIn(DataLocation _loc) const override
|
||||
{
|
||||
solAssert(!underlyingType().dataStoredIn(_loc), "");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string toString(bool _short) const override;
|
||||
std::string canonicalName() const override { solAssert(false, ""); }
|
||||
std::string signatureInExternalFunction(bool) const override { solAssert(false, ""); }
|
||||
|
||||
protected:
|
||||
std::vector<std::tuple<std::string, Type const*>> makeStackItems() const override;
|
||||
|
||||
private:
|
||||
UserDefinedValueTypeDefinition const& m_definition;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type that can hold a finite sequence of values of different types.
|
||||
* In some cases, the components are empty pointers (when used as placeholders).
|
||||
@ -1144,6 +1219,8 @@ public:
|
||||
RIPEMD160, ///< CALL to special contract for ripemd160
|
||||
Event, ///< syntactic sugar for LOG*
|
||||
Error, ///< creating an error instance in revert or require
|
||||
Wrap, ///< customType.wrap(...) for user defined value types
|
||||
Unwrap, ///< customType.unwrap(...) for user defined value types
|
||||
SetGas, ///< modify the default gas value for the function call
|
||||
SetValue, ///< modify the default value transfer for the function call
|
||||
BlockHash, ///< BLOCKHASH
|
||||
|
@ -1170,6 +1170,7 @@ string ABIFunctions::abiDecodingFunctionArrayAvailableLength(ArrayType const& _t
|
||||
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
|
||||
if (_type.isByteArray())
|
||||
return abiDecodingFunctionByteArrayAvailableLength(_type, _fromMemory);
|
||||
solAssert(_type.calldataStride() > 0, "");
|
||||
|
||||
string functionName =
|
||||
"abi_decode_available_length_" +
|
||||
@ -1186,11 +1187,11 @@ string ABIFunctions::abiDecodingFunctionArrayAvailableLength(ArrayType const& _t
|
||||
mstore(array, length)
|
||||
dst := add(array, 0x20)
|
||||
</dynamic>
|
||||
let src := offset
|
||||
if gt(add(src, mul(length, <stride>)), end) {
|
||||
let srcEnd := add(offset, mul(length, <stride>))
|
||||
if gt(srcEnd, end) {
|
||||
<revertInvalidStride>()
|
||||
}
|
||||
for { let i := 0 } lt(i, length) { i := add(i, 1) }
|
||||
for { let src := offset } lt(src, srcEnd) { src := add(src, <stride>) }
|
||||
{
|
||||
<?dynamicBase>
|
||||
let innerOffset := <load>(src)
|
||||
@ -1201,7 +1202,6 @@ string ABIFunctions::abiDecodingFunctionArrayAvailableLength(ArrayType const& _t
|
||||
</dynamicBase>
|
||||
mstore(dst, <decodingFun>(elementPos, end))
|
||||
dst := add(dst, 0x20)
|
||||
src := add(src, <stride>)
|
||||
}
|
||||
}
|
||||
)");
|
||||
|
@ -60,8 +60,3 @@ std::shared_ptr<evmasm::Assembly> Compiler::runtimeAssemblyPtr() const
|
||||
solAssert(m_context.runtimeContext(), "");
|
||||
return m_context.runtimeContext()->assemblyPtr();
|
||||
}
|
||||
|
||||
evmasm::AssemblyItem Compiler::functionEntryLabel(FunctionDefinition const& _function) const
|
||||
{
|
||||
return m_runtimeContext.functionEntryLabelIfExists(_function);
|
||||
}
|
||||
|
@ -62,10 +62,6 @@ public:
|
||||
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;
|
||||
|
||||
private:
|
||||
OptimiserSettings const m_optimiserSettings;
|
||||
CompilerContext m_runtimeContext;
|
||||
|
@ -421,7 +421,7 @@ void CompilerContext::appendInlineAssembly(
|
||||
if (stackDiff < 1 || stackDiff > 16)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
StackTooDeepError() <<
|
||||
errinfo_sourceLocation(_identifier.debugData->location) <<
|
||||
errinfo_sourceLocation(nativeLocationOf(_identifier)) <<
|
||||
util::errinfo_comment("Stack too deep (" + to_string(stackDiff) + "), try removing local variables.")
|
||||
);
|
||||
if (_context == yul::IdentifierContext::RValue)
|
||||
|
@ -46,6 +46,7 @@
|
||||
#include <stack>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
#include <limits>
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
@ -40,7 +40,7 @@ using namespace solidity::langutil;
|
||||
|
||||
using solidity::util::Whiskers;
|
||||
using solidity::util::h256;
|
||||
using solidity::util::toCompactHexWithPrefix;
|
||||
using solidity::toCompactHexWithPrefix;
|
||||
|
||||
unsigned const CompilerUtils::dataStartOffset = 4;
|
||||
size_t const CompilerUtils::freeMemoryPointer = 64;
|
||||
@ -772,6 +772,33 @@ void CompilerUtils::convertType(
|
||||
Type::Category stackTypeCategory = _typeOnStack.category();
|
||||
Type::Category targetTypeCategory = _targetType.category();
|
||||
|
||||
if (stackTypeCategory == Type::Category::UserDefinedValueType)
|
||||
{
|
||||
solAssert(_cleanupNeeded, "");
|
||||
auto& userDefined = dynamic_cast<UserDefinedValueType const&>(_typeOnStack);
|
||||
solAssert(_typeOnStack == _targetType || _targetType == userDefined.underlyingType(), "");
|
||||
return convertType(
|
||||
userDefined.underlyingType(),
|
||||
_targetType,
|
||||
_cleanupNeeded,
|
||||
_chopSignBits,
|
||||
_asPartOfArgumentDecoding
|
||||
);
|
||||
}
|
||||
if (targetTypeCategory == Type::Category::UserDefinedValueType)
|
||||
{
|
||||
solAssert(_cleanupNeeded, "");
|
||||
auto& userDefined = dynamic_cast<UserDefinedValueType const&>(_targetType);
|
||||
solAssert(_typeOnStack.isImplicitlyConvertibleTo(userDefined.underlyingType()), "");
|
||||
return convertType(
|
||||
_typeOnStack,
|
||||
userDefined.underlyingType(),
|
||||
_cleanupNeeded,
|
||||
_chopSignBits,
|
||||
_asPartOfArgumentDecoding
|
||||
);
|
||||
}
|
||||
|
||||
if (auto contrType = dynamic_cast<ContractType const*>(&_typeOnStack))
|
||||
solAssert(!contrType->isSuper(), "Cannot convert magic variable \"super\"");
|
||||
|
||||
@ -1525,10 +1552,13 @@ void CompilerUtils::storeStringData(bytesConstRef _data)
|
||||
unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWords)
|
||||
{
|
||||
solAssert(_type.isValueType(), "");
|
||||
Type const* type = &_type;
|
||||
if (auto const* userDefined = dynamic_cast<UserDefinedValueType const*>(type))
|
||||
type = &userDefined->underlyingType();
|
||||
|
||||
unsigned numBytes = _type.calldataEncodedSize(_padToWords);
|
||||
unsigned numBytes = type->calldataEncodedSize(_padToWords);
|
||||
bool isExternalFunctionType = false;
|
||||
if (auto const* funType = dynamic_cast<FunctionType const*>(&_type))
|
||||
if (auto const* funType = dynamic_cast<FunctionType const*>(type))
|
||||
if (funType->kind() == FunctionType::Kind::External)
|
||||
isExternalFunctionType = true;
|
||||
if (numBytes == 0)
|
||||
@ -1543,21 +1573,20 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda
|
||||
splitExternalFunctionType(true);
|
||||
else if (numBytes != 32)
|
||||
{
|
||||
bool leftAligned = _type.category() == Type::Category::FixedBytes;
|
||||
// add leading or trailing zeros by dividing/multiplying depending on alignment
|
||||
unsigned shiftFactor = (32 - numBytes) * 8;
|
||||
rightShiftNumberOnStack(shiftFactor);
|
||||
if (leftAligned)
|
||||
if (type->leftAligned())
|
||||
{
|
||||
leftShiftNumberOnStack(shiftFactor);
|
||||
cleanupNeeded = false;
|
||||
}
|
||||
else if (IntegerType const* intType = dynamic_cast<IntegerType const*>(&_type))
|
||||
else if (IntegerType const* intType = dynamic_cast<IntegerType const*>(type))
|
||||
if (!intType->isSigned())
|
||||
cleanupNeeded = false;
|
||||
}
|
||||
if (_fromCalldata)
|
||||
convertType(_type, _type, cleanupNeeded, false, true);
|
||||
convertType(_type, *type, cleanupNeeded, false, true);
|
||||
|
||||
return numBytes;
|
||||
}
|
||||
@ -1612,12 +1641,10 @@ unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords,
|
||||
"Memory store of more than 32 bytes requested (Type: " + _type.toString(true) + ")."
|
||||
);
|
||||
|
||||
bool leftAligned = _type.category() == Type::Category::FixedBytes;
|
||||
|
||||
if (_cleanup)
|
||||
convertType(_type, _type, true);
|
||||
|
||||
if (numBytes != 32 && !leftAligned && !_padToWords)
|
||||
if (numBytes != 32 && !_type.leftAligned() && !_padToWords)
|
||||
// shift the value accordingly before storing
|
||||
leftShiftNumberOnStack((32 - numBytes) * 8);
|
||||
|
||||
|
@ -95,9 +95,9 @@ public:
|
||||
/// @returns the number of bytes consumed in memory.
|
||||
unsigned loadFromMemory(
|
||||
unsigned _offset,
|
||||
Type const& _type = *TypeProvider::uint256(),
|
||||
bool _fromCalldata = false,
|
||||
bool _padToWords = false
|
||||
Type const& _type,
|
||||
bool _fromCalldata,
|
||||
bool _padToWords
|
||||
);
|
||||
/// Dynamic version of @see loadFromMemory, expects the memory offset on the stack.
|
||||
/// Stack pre: memory_offset
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user