Merge remote-tracking branch 'origin/develop' into breaking

This commit is contained in:
chriseth 2021-10-06 12:00:17 +02:00
commit 8bcbe946c6
733 changed files with 18775 additions and 5313 deletions

View File

@ -18,7 +18,7 @@ else {
mkdir build mkdir build
cd build cd build
$boost_dir=(Resolve-Path $PSScriptRoot\..\deps\boost\lib\cmake\Boost-*) $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." } if ( -not $? ) { throw "CMake configure failed." }
msbuild solidity.sln /p:Configuration=Release /m:5 /v:minimal msbuild solidity.sln /p:Configuration=Release /m:5 /v:minimal
if ( -not $? ) { throw "Build failed." } if ( -not $? ) { throw "Build failed." }

View File

@ -17,12 +17,15 @@ parameters:
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:61232feea23c8c57e82cf5fae890f8b86bbec353cdc04f2fcba383ca589e1d8b" default: "solbuildpackpusher/solidity-buildpack-deps@sha256:61232feea23c8c57e82cf5fae890f8b86bbec353cdc04f2fcba383ca589e1d8b"
ubuntu-1604-clang-ossfuzz-docker-image: ubuntu-1604-clang-ossfuzz-docker-image:
type: string type: string
# solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-11 # solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-13
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:4acb2674eab3e7939d6dc6caa0b8320f4dd79484325242b58473ca2875792d90" default: "solbuildpackpusher/solidity-buildpack-deps@sha256:c26a7ffc9fc243a4ec3105b9dc1edcdd964ad0e9665c83172b7ebda74bbf3021"
emscripten-docker-image: emscripten-docker-image:
type: string type: string
# solbuildpackpusher/solidity-buildpack-deps:emscripten-6 # solbuildpackpusher/solidity-buildpack-deps:emscripten-6
default: "solbuildpackpusher/solidity-buildpack-deps@sha256:092da5817bc032c91a806b4f73db2a1a31e5cc4c066d94d43eedd9f365df7154" default: "solbuildpackpusher/solidity-buildpack-deps@sha256:092da5817bc032c91a806b4f73db2a1a31e5cc4c066d94d43eedd9f365df7154"
evm-version:
type: string
default: london
orbs: orbs:
win: circleci/windows@2.2.0 win: circleci/windows@2.2.0
@ -50,6 +53,25 @@ defaults:
name: Correctness proofs for optimization rules name: Correctness proofs for optimization rules
command: scripts/run_proofs.sh 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 # Artifacts Templates
@ -111,17 +133,7 @@ defaults:
- store_test_results: &store_test_results - store_test_results: &store_test_results
path: test_results/ path: test_results/
- run_soltest: &run_soltest - steps_soltest: &steps_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: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -130,7 +142,7 @@ defaults:
- store_test_results: *store_test_results - store_test_results: *store_test_results
- store_artifacts: *artifacts_test_results - store_artifacts: *artifacts_test_results
- run_soltest_all_steps: &run_soltest_all_steps - steps_soltest_all: &steps_soltest_all
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -139,12 +151,7 @@ defaults:
- store_test_results: *store_test_results - store_test_results: *store_test_results
- store_artifacts: *artifacts_test_results - store_artifacts: *artifacts_test_results
- run_cmdline_tests: &run_cmdline_tests - steps_cmdline_tests: &steps_cmdline_tests
name: command line tests
no_output_timeout: 30m
command: ./test/cmdlineTests.sh
- run_cmdline_tests_steps: &run_cmdline_tests_steps
steps: steps:
- checkout - checkout
- attach_workspace: - attach_workspace:
@ -153,34 +160,30 @@ defaults:
- store_test_results: *store_test_results - store_test_results: *store_test_results
- store_artifacts: *artifacts_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 - test_ubuntu1604_clang: &test_ubuntu1604_clang
docker: docker:
- image: << pipeline.parameters.ubuntu-1604-clang-ossfuzz-docker-image >> - image: << pipeline.parameters.ubuntu-1604-clang-ossfuzz-docker-image >>
<<: *run_soltest_steps <<: *steps_soltest
- test_ubuntu2004_clang: &test_ubuntu2004_clang - test_ubuntu2004_clang: &test_ubuntu2004_clang
docker: docker:
- image: << pipeline.parameters.ubuntu-2004-clang-docker-image >> - image: << pipeline.parameters.ubuntu-2004-clang-docker-image >>
<<: *run_soltest_steps <<: *steps_soltest
- test_ubuntu2004: &test_ubuntu2004 - test_ubuntu2004: &test_ubuntu2004
docker: docker:
- image: << pipeline.parameters.ubuntu-2004-docker-image >> - image: << pipeline.parameters.ubuntu-2004-docker-image >>
parallelism: 6 parallelism: 6
<<: *run_soltest_all_steps <<: *steps_soltest_all
- test_asan: &test_asan - test_asan: &test_asan
<<: *test_ubuntu2004 <<: *test_ubuntu2004
<<: *run_soltest_steps <<: *steps_soltest
- test_ubuntu2004_clang_cli: &test_ubuntu2004_clang_cli - test_ubuntu2004_clang_cli: &test_ubuntu2004_clang_cli
docker: docker:
- image: << pipeline.parameters.ubuntu-2004-clang-docker-image >> - image: << pipeline.parameters.ubuntu-2004-clang-docker-image >>
<<: *run_cmdline_tests_steps <<: *steps_cmdline_tests
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# Workflow Templates # Workflow Templates
@ -434,6 +437,32 @@ jobs:
name: Python unit tests name: Python unit tests
command: python.exe test/pyscriptTests.py 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 b_ubu_clang: &b_ubu_clang
resource_class: xlarge resource_class: xlarge
docker: docker:
@ -477,19 +506,6 @@ jobs:
- store_artifacts: *artifacts_solc - store_artifacts: *artifacts_solc
- persist_to_workspace: *artifacts_executables - 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_release: &b_ubu_release
<<: *b_ubu <<: *b_ubu
environment: environment:
@ -523,7 +539,7 @@ jobs:
t_ubu_codecov: t_ubu_codecov:
<<: *test_ubuntu2004 <<: *test_ubuntu2004
environment: environment:
EVM: constantinople EVM: << pipeline.parameters.evm-version >>
OPTIMIZE: 1 OPTIMIZE: 1
steps: steps:
- checkout - checkout
@ -586,9 +602,7 @@ jobs:
b_archlinux: b_archlinux:
docker: docker:
# FIXME: Newer releases won't work until CircleCI updates its host machines. - image: archlinux:base
# See https://github.com/ethereum/solidity/pull/11332
- image: archlinux:base-20210131.0.14634
environment: environment:
TERM: xterm TERM: xterm
MAKEFLAGS: -j 3 MAKEFLAGS: -j 3
@ -642,7 +656,7 @@ jobs:
macos: macos:
xcode: "11.0.0" xcode: "11.0.0"
environment: environment:
EVM: constantinople EVM: << pipeline.parameters.evm-version >>
OPTIMIZE: 0 OPTIMIZE: 0
TERM: xterm TERM: xterm
steps: steps:
@ -696,19 +710,6 @@ jobs:
- soljson.js - soljson.js
- version.txt - 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: b_docs:
docker: docker:
- image: << pipeline.parameters.ubuntu-2004-docker-image >> - image: << pipeline.parameters.ubuntu-2004-docker-image >>
@ -727,52 +728,38 @@ jobs:
t_archlinux_soltest: &t_archlinux_soltest t_archlinux_soltest: &t_archlinux_soltest
docker: docker:
# FIXME: Newer releases won't work until CircleCI updates its host machines. - image: archlinux:base
# See https://github.com/ethereum/solidity/pull/11332
- image: archlinux:base-20210131.0.14634
environment: environment:
EVM: constantinople EVM: << pipeline.parameters.evm-version >>
OPTIMIZE: 0 OPTIMIZE: 0
TERM: xterm TERM: xterm
# For Archlinux we do not have prebuilt docker images and we would need to build evmone from source, # 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. # 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 SOLTEST_FLAGS: --no-semantic-tests
# 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
steps: steps:
- run: - run:
name: Install runtime dependencies name: Install runtime dependencies
command: | command: |
pacman --noconfirm -Syu --noprogressbar --needed base-devel boost cmake z3 cvc4 git openssh tar pacman --noconfirm -Syu --noprogressbar --needed base-devel boost cmake z3 cvc4 git openssh tar
- checkout - when:
- attach_workspace: condition: true
at: build <<: *steps_soltest
- run: *run_soltest
- store_test_results: *store_test_results
- store_artifacts: *artifacts_test_results
t_ubu_soltest_enforce_yul: &t_ubu_soltest_enforce_yul t_ubu_soltest_enforce_yul: &t_ubu_soltest_enforce_yul
docker: docker:
- image: << pipeline.parameters.ubuntu-2004-docker-image >> - image: << pipeline.parameters.ubuntu-2004-docker-image >>
environment: environment:
EVM: constantinople EVM: << pipeline.parameters.evm-version >>
SOLTEST_FLAGS: --enforce-via-yul SOLTEST_FLAGS: --enforce-via-yul
OPTIMIZE: 0 OPTIMIZE: 0
TERM: xterm TERM: xterm
steps: <<: *steps_soltest
- checkout
- attach_workspace:
at: build
- run: *run_soltest
- store_test_results: *store_test_results
- store_artifacts: *artifacts_test_results
t_ubu_clang_soltest: &t_ubu_clang_soltest t_ubu_clang_soltest: &t_ubu_clang_soltest
<<: *test_ubuntu2004_clang <<: *test_ubuntu2004_clang
environment: environment:
EVM: constantinople EVM: << pipeline.parameters.evm-version >>
OPTIMIZE: 0 OPTIMIZE: 0
t_ubu_release_soltest: &t_ubu_release_soltest t_ubu_release_soltest: &t_ubu_release_soltest
@ -783,7 +770,7 @@ jobs:
- image: << pipeline.parameters.ubuntu-2004-docker-image >> - image: << pipeline.parameters.ubuntu-2004-docker-image >>
environment: environment:
TERM: xterm TERM: xterm
<<: *run_cmdline_tests_steps <<: *steps_cmdline_tests
t_ubu_release_cli: &t_ubu_release_cli t_ubu_release_cli: &t_ubu_release_cli
<<: *t_ubu_cli <<: *t_ubu_cli
@ -793,47 +780,43 @@ jobs:
environment: environment:
TERM: xterm 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 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 <<: *test_asan
environment: environment:
EVM: constantinople EVM: << pipeline.parameters.evm-version >>
OPTIMIZE: 0 OPTIMIZE: 0
SOLTEST_FLAGS: --no-smt 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 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 <<: *test_ubuntu2004_clang
environment: environment:
EVM: constantinople EVM: << pipeline.parameters.evm-version >>
OPTIMIZE: 0 OPTIMIZE: 0
SOLTEST_FLAGS: --no-smt 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 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: t_ubu_ubsan_clang:
environment:
EVM: << pipeline.parameters.evm-version >>
docker: docker:
- image: << pipeline.parameters.ubuntu-2004-clang-docker-image >> - image: << pipeline.parameters.ubuntu-2004-clang-docker-image >>
steps: steps:
- checkout - when:
- attach_workspace: condition: true
at: build <<: *steps_soltest
- run: *run_soltest
- run: *gitter_notify_failure - run: *gitter_notify_failure
- store_test_results: *store_test_results
- store_artifacts: *artifacts_test_results
t_ubu_ubsan_clang_cli: t_ubu_ubsan_clang_cli:
docker: docker:
- image: << pipeline.parameters.ubuntu-2004-clang-docker-image >> - image: << pipeline.parameters.ubuntu-2004-clang-docker-image >>
steps: steps:
- checkout - when:
- attach_workspace: condition: true
at: build <<: *steps_cmdline_tests
- run: *run_cmdline_tests
- run: *gitter_notify_failure - run: *gitter_notify_failure
- store_test_results: *store_test_results
- store_artifacts: *artifacts_test_results
t_ems_solcjs: t_ems_solcjs:
docker: docker:
@ -1024,7 +1007,7 @@ jobs:
b_bytecode_ems: b_bytecode_ems:
docker: docker:
- image: circleci/node:14 - image: circleci/node:16
environment: environment:
SOLC_EMSCRIPTEN: "On" SOLC_EMSCRIPTEN: "On"
steps: steps:
@ -1216,7 +1199,6 @@ workflows:
branches: branches:
only: only:
- develop - develop
- develop_060
jobs: jobs:
# OSSFUZZ builds and (regression) tests # OSSFUZZ builds and (regression) tests
@ -1230,8 +1212,8 @@ workflows:
# ASan build and tests # ASan build and tests
- b_ubu_asan: *workflow_trigger_on_tags - b_ubu_asan: *workflow_trigger_on_tags
- b_ubu_asan_clang: *workflow_trigger_on_tags - b_ubu_asan_clang: *workflow_trigger_on_tags
- t_ubu_asan_constantinople: *workflow_ubuntu2004_asan - t_ubu_asan: *workflow_ubuntu2004_asan
- t_ubu_asan_constantinople_clang: *workflow_ubuntu2004_asan_clang - t_ubu_asan_clang: *workflow_ubuntu2004_asan_clang
- t_ubu_asan_cli: *workflow_ubuntu2004_asan - t_ubu_asan_cli: *workflow_ubuntu2004_asan
# UBSan build and tests # UBSan build and tests

View File

@ -35,6 +35,21 @@
set -eu 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 if [ ! -f /usr/local/lib/libz3.a ] # if this file does not exists (cache was not restored), rebuild dependencies
then then
git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow 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 ./scripts/install_obsolete_jsoncpp_1_7_4.sh
# z3 # z3
z3_version="z3-4.8.12" z3_version="4.8.12"
osx_version="osx-10.15.7" z3_dir="z3-${z3_version}-x64-osx-10.15.7"
wget "https://github.com/Z3Prover/z3/releases/download/$z3_version/$z3_version-x64-$osx_version.zip" z3_package="${z3_dir}.zip"
unzip "$z3_version-x64-$osx_version.zip" wget "https://github.com/Z3Prover/z3/releases/download/z3-${z3_version}/${z3_package}"
rm -f "$z3_version-x64-$osx_version.zip" validate_checksum "$z3_package" a1f6ef3c99456147c4d3f2652dc6bc90951c4ab3fe7741a255eb794f0ab8938c
cp "$z3_version-x64-$osx_version/bin/libz3.a" /usr/local/lib unzip "$z3_package"
cp "$z3_version-x64-$osx_version/bin/z3" /usr/local/bin rm "$z3_package"
cp "$z3_version-x64-$osx_version"/include/* /usr/local/include cp "${z3_dir}/bin/libz3.a" /usr/local/lib
rm -rf "$z3_version-x64-$osx_version" cp "${z3_dir}/bin/z3" /usr/local/bin
cp "${z3_dir}/include/"* /usr/local/include
rm -r "$z3_dir"
# evmone # evmone
wget https://github.com/ethereum/evmone/releases/download/v0.8.0/evmone-0.8.0-darwin-x86_64.tar.gz evmone_version="0.8.0"
tar xzpf evmone-0.8.0-darwin-x86_64.tar.gz -C /usr/local evmone_package="evmone-${evmone_version}-darwin-x86_64.tar.gz"
rm -f evmone-0.8.0-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 # hera
wget https://github.com/ewasm/hera/releases/download/v0.5.0/hera-0.5.0-darwin-x86_64.tar.gz hera_version="0.5.0"
tar xzpf hera-0.5.0-darwin-x86_64.tar.gz -C /usr/local hera_package="hera-${hera_version}-darwin-x86_64.tar.gz"
rm -f hera-0.5.0-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 fi

View File

@ -21,7 +21,7 @@ include(EthPolicy)
eth_policy() eth_policy()
# project name and version should be set after cmake_policy CMP0048 # 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 # OSX target needed in order to support std::visit
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX) 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_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(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. # Setup cccache.
include(EthCcache) include(EthCcache)
@ -63,8 +64,29 @@ configure_file("${CMAKE_SOURCE_DIR}/cmake/templates/license.h.in" include/licens
include(EthOptions) include(EthOptions)
configure_project(TESTS) 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}) if(${USE_Z3_DLOPEN})
add_definitions(-DHAVE_Z3) add_definitions(-DHAVE_Z3)
add_definitions(-DHAVE_Z3_DLOPEN) add_definitions(-DHAVE_Z3_DLOPEN)

View File

@ -81,7 +81,7 @@ tuple<float, float> myNamespace::meanAndSigma(vector<float> const& _v)
- Copyright - Copyright
- License (e.g. see COPYING) - License (e.g. see COPYING)
2. Never use `#ifdef`/`#define`/`#endif` file guards. Prefer `#pragma` once as first line below file comment. 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. 4. Prefer inline constexpr functions to function macros.
5. Split complex macro on multiple lines with `\`. 5. Split complex macro on multiple lines with `\`.

View File

@ -6,23 +6,72 @@ Breaking changes:
* Inline Assembly: Consider functions, function parameters and return variables for shadowing checks. * Inline Assembly: Consider functions, function parameters and return variables for shadowing checks.
### 0.8.8 (unreleased) ### 0.8.10 (unreleased)
Language Features: Language Features:
* Inline Assembly: Support ``.address`` and ``.selector`` on external function pointers to access their address and function selector.
Compiler Features: Compiler Features:
* Commandline Interface: Normalize paths specified on the command line and make them relative for files located inside base path. * SMTChecker: Output values for ``block.*``, ``msg.*`` and ``tx.*`` variables that are present in the called functions.
* 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.
Bugfixes: 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 false positive in external calls from constructors.
* SMTChecker: Fix internal error on some multi-source uses of ``abi.*``, cryptographic functions and constants. * 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.

View File

@ -76,7 +76,7 @@ Please follow the
if you want to help. if you want to help.
You can find our current feature and bug priorities for forthcoming 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 ## Maintainers
* [@axic](https://github.com/axic) * [@axic](https://github.com/axic)

View File

@ -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. # Some Linux-specific Clang settings. We don't want these for OS X.
if ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") 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 # Use fancy colors in the compiler diagnostics
add_compile_options(-fcolor-diagnostics) add_compile_options(-fcolor-diagnostics)
@ -200,6 +192,10 @@ if (SANITIZE)
elseif (sanitizer STREQUAL "undefined") elseif (sanitizer STREQUAL "undefined")
# The following flags not used by fuzzer but used by us may create problems, so consider # The following flags not used by fuzzer but used by us may create problems, so consider
# disabling them: alignment, pointer-overflow. # 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 # Flags are alphabetically sorted and are for clang v10.0
list(APPEND undefinedSanitizerChecks list(APPEND undefinedSanitizerChecks
alignment alignment
@ -217,18 +213,12 @@ if (SANITIZE)
returns-nonnull-attribute returns-nonnull-attribute
shift shift
signed-integer-overflow signed-integer-overflow
unsigned-integer-overflow
unreachable unreachable
vla-bound vla-bound
vptr vptr
) )
list(JOIN undefinedSanitizerChecks "," sanitizerChecks) list(JOIN undefinedSanitizerChecks "," sanitizerChecks)
list(REMOVE_ITEM undefinedSanitizerChecks unsigned-integer-overflow) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=${sanitizerChecks} -fno-sanitize-recover=${sanitizerChecks}")
# 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}")
endif() endif()
endif() endif()

View File

@ -25,13 +25,20 @@ set(ETH_SCRIPTS_DIR ${ETH_CMAKE_DIR}/scripts)
## use multithreaded boost libraries, with -mt suffix ## use multithreaded boost libraries, with -mt suffix
set(Boost_USE_MULTITHREADED ON) set(Boost_USE_MULTITHREADED ON)
option(Boost_USE_STATIC_LIBS "Link Boost statically" 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) option(Boost_USE_STATIC_RUNTIME "Link Boost against static C++ runtime libraries" ON)
endif() endif()
set(BOOST_COMPONENTS "filesystem;unit_test_framework;program_options;system") 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, # If cmake is older than boost and boost is older than 1.70,
# find_package does not define imported targets, so we have to # find_package does not define imported targets, so we have to
@ -51,7 +58,7 @@ foreach (BOOST_COMPONENT IN LISTS BOOST_COMPONENTS)
set_property(TARGET Boost::${BOOST_COMPONENT} PROPERTY IMPORTED_LOCATION ${Boost_${BOOST_COMPONENT_UPPER}_LIBRARY}) set_property(TARGET Boost::${BOOST_COMPONENT} PROPERTY IMPORTED_LOCATION ${Boost_${BOOST_COMPONENT_UPPER}_LIBRARY})
set_property(TARGET Boost::${BOOST_COMPONENT} PROPERTY INTERFACE_LINK_LIBRARIES ${Boost_${BOOST_COMPONENT_UPPER}_LIBRARIES}) set_property(TARGET Boost::${BOOST_COMPONENT} PROPERTY INTERFACE_LINK_LIBRARIES ${Boost_${BOOST_COMPONENT_UPPER}_LIBRARIES})
set_property(TARGET Boost::${BOOST_COMPONENT} PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Boost_INCLUDE_DIRS}) set_property(TARGET Boost::${BOOST_COMPONENT} PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Boost_INCLUDE_DIRS})
endif() endif()
get_property(LOCATION TARGET Boost::${BOOST_COMPONENT} PROPERTY IMPORTED_LOCATION) get_property(LOCATION TARGET Boost::${BOOST_COMPONENT} PROPERTY IMPORTED_LOCATION)
message(STATUS "Found Boost::${BOOST_COMPONENT} at ${LOCATION}") message(STATUS "Found Boost::${BOOST_COMPONENT} at ${LOCATION}")
endforeach() endforeach()

View File

@ -113,6 +113,9 @@ them.
+-------------------------------+-----------------------------------------------------------------------------+ +-------------------------------+-----------------------------------------------------------------------------+
|:ref:`enum<enums>` |``uint8`` | |:ref:`enum<enums>` |``uint8`` |
+-------------------------------+-----------------------------------------------------------------------------+ +-------------------------------+-----------------------------------------------------------------------------+
|:ref:`user defined value types |its underlying value type |
|<user-defined-value-types>` | |
+-------------------------------+-----------------------------------------------------------------------------+
|:ref:`struct<structs>` |``tuple`` | |:ref:`struct<structs>` |``tuple`` |
+-------------------------------+-----------------------------------------------------------------------------+ +-------------------------------+-----------------------------------------------------------------------------+

View File

@ -35,9 +35,9 @@ Example
------- -------
The following example provides library code to access the code of another contract and 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 load it into a ``bytes`` variable. This is possible with "plain Solidity" too, by using
idea is that reusable assembly libraries can enhance the Solidity language ``<address>.code``. But the point here is that reusable assembly libraries can enhance the
without a compiler change. Solidity language without a compiler change.
.. code-block:: solidity .. 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. You can access Solidity variables and other identifiers by using their name.
Local variables of value type are directly usable in inline assembly. 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 Local variables that refer to memory evaluate to the address of the variable in memory not the value itself.
address of the variable in memory, resp. calldata, 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 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. 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``. use ``x.slot``, and to retrieve the byte-offset you use ``x.offset``.
Using ``x`` itself will result in an error. Using ``x`` itself will result in an error.
For dynamic calldata arrays, you can access You can also assign to the ``.slot`` part of a local storage variable pointer.
their calldata offset (in bytes) and length (number of elements) using ``x.offset`` and ``x.length``. For these (structs, arrays or mappings), the ``.offset`` part is always zero.
Both expressions can also be assigned to. 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: 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 inline assembly block may not contain ``.``, but using ``.`` is
valid to access Solidity variables from outside the inline assembly block. 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 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 variable access and removing stack slots for assembly-local variables when the end
of their block is reached. of their block is reached.
.. _conventions-in-solidity:
Conventions in Solidity Conventions in Solidity
----------------------- -----------------------

View File

@ -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", "uid": "SOL-2021-2",
"name": "ABIDecodeTwoDimensionalArrayMemory", "name": "ABIDecodeTwoDimensionalArrayMemory",

View File

@ -1333,6 +1333,7 @@
}, },
"0.6.10": { "0.6.10": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory", "ABIDecodeTwoDimensionalArrayMemory",
"KeccakCaching", "KeccakCaching",
"EmptyByteArrayCopy", "EmptyByteArrayCopy",
@ -1342,6 +1343,7 @@
}, },
"0.6.11": { "0.6.11": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory", "ABIDecodeTwoDimensionalArrayMemory",
"KeccakCaching", "KeccakCaching",
"EmptyByteArrayCopy", "EmptyByteArrayCopy",
@ -1351,6 +1353,7 @@
}, },
"0.6.12": { "0.6.12": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory", "ABIDecodeTwoDimensionalArrayMemory",
"KeccakCaching", "KeccakCaching",
"EmptyByteArrayCopy", "EmptyByteArrayCopy",
@ -1402,6 +1405,7 @@
}, },
"0.6.5": { "0.6.5": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory", "ABIDecodeTwoDimensionalArrayMemory",
"KeccakCaching", "KeccakCaching",
"EmptyByteArrayCopy", "EmptyByteArrayCopy",
@ -1415,6 +1419,7 @@
}, },
"0.6.6": { "0.6.6": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory", "ABIDecodeTwoDimensionalArrayMemory",
"KeccakCaching", "KeccakCaching",
"EmptyByteArrayCopy", "EmptyByteArrayCopy",
@ -1427,6 +1432,7 @@
}, },
"0.6.7": { "0.6.7": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory", "ABIDecodeTwoDimensionalArrayMemory",
"KeccakCaching", "KeccakCaching",
"EmptyByteArrayCopy", "EmptyByteArrayCopy",
@ -1439,6 +1445,7 @@
}, },
"0.6.8": { "0.6.8": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory", "ABIDecodeTwoDimensionalArrayMemory",
"KeccakCaching", "KeccakCaching",
"EmptyByteArrayCopy", "EmptyByteArrayCopy",
@ -1448,6 +1455,7 @@
}, },
"0.6.9": { "0.6.9": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory", "ABIDecodeTwoDimensionalArrayMemory",
"KeccakCaching", "KeccakCaching",
"EmptyByteArrayCopy", "EmptyByteArrayCopy",
@ -1458,6 +1466,7 @@
}, },
"0.7.0": { "0.7.0": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory", "ABIDecodeTwoDimensionalArrayMemory",
"KeccakCaching", "KeccakCaching",
"EmptyByteArrayCopy", "EmptyByteArrayCopy",
@ -1467,6 +1476,7 @@
}, },
"0.7.1": { "0.7.1": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory", "ABIDecodeTwoDimensionalArrayMemory",
"KeccakCaching", "KeccakCaching",
"EmptyByteArrayCopy", "EmptyByteArrayCopy",
@ -1477,6 +1487,7 @@
}, },
"0.7.2": { "0.7.2": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory", "ABIDecodeTwoDimensionalArrayMemory",
"KeccakCaching", "KeccakCaching",
"EmptyByteArrayCopy", "EmptyByteArrayCopy",
@ -1486,6 +1497,7 @@
}, },
"0.7.3": { "0.7.3": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory", "ABIDecodeTwoDimensionalArrayMemory",
"KeccakCaching", "KeccakCaching",
"EmptyByteArrayCopy" "EmptyByteArrayCopy"
@ -1494,6 +1506,7 @@
}, },
"0.7.4": { "0.7.4": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory", "ABIDecodeTwoDimensionalArrayMemory",
"KeccakCaching" "KeccakCaching"
], ],
@ -1501,6 +1514,7 @@
}, },
"0.7.5": { "0.7.5": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory", "ABIDecodeTwoDimensionalArrayMemory",
"KeccakCaching" "KeccakCaching"
], ],
@ -1508,6 +1522,7 @@
}, },
"0.7.6": { "0.7.6": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory", "ABIDecodeTwoDimensionalArrayMemory",
"KeccakCaching" "KeccakCaching"
], ],
@ -1515,6 +1530,7 @@
}, },
"0.8.0": { "0.8.0": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory", "ABIDecodeTwoDimensionalArrayMemory",
"KeccakCaching" "KeccakCaching"
], ],
@ -1522,6 +1538,7 @@
}, },
"0.8.1": { "0.8.1": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory", "ABIDecodeTwoDimensionalArrayMemory",
"KeccakCaching" "KeccakCaching"
], ],
@ -1529,6 +1546,7 @@
}, },
"0.8.2": { "0.8.2": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory", "ABIDecodeTwoDimensionalArrayMemory",
"KeccakCaching" "KeccakCaching"
], ],
@ -1536,24 +1554,44 @@
}, },
"0.8.3": { "0.8.3": {
"bugs": [ "bugs": [
"SignedImmutables",
"ABIDecodeTwoDimensionalArrayMemory" "ABIDecodeTwoDimensionalArrayMemory"
], ],
"released": "2021-03-23" "released": "2021-03-23"
}, },
"0.8.4": { "0.8.4": {
"bugs": [], "bugs": [
"SignedImmutables"
],
"released": "2021-04-21" "released": "2021-04-21"
}, },
"0.8.5": { "0.8.5": {
"bugs": [], "bugs": [
"SignedImmutables"
],
"released": "2021-06-10" "released": "2021-06-10"
}, },
"0.8.6": { "0.8.6": {
"bugs": [], "bugs": [
"SignedImmutables"
],
"released": "2021-06-22" "released": "2021-06-22"
}, },
"0.8.7": { "0.8.7": {
"bugs": [], "bugs": [
"SignedImmutables"
],
"released": "2021-08-11" "released": "2021-08-11"
},
"0.8.8": {
"bugs": [
"UserDefinedValueTypesBug",
"SignedImmutables"
],
"released": "2021-09-27"
},
"0.8.9": {
"bugs": [],
"released": "2021-09-29"
} }
} }

View File

@ -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 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>`. 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 .. index:: ! view function, function;view
.. _view-functions: .. _view-functions:
View Functions View Functions
============== --------------
Functions can be declared ``view`` in which case they promise not to modify the state. 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:
Pure Functions Pure Functions
============== --------------
Functions can be declared ``pure`` in which case they promise not to read from or modify the state. 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 not do state-changing operations, but it cannot check that the contract that will be called
at runtime is actually of that type. at runtime is actually of that type.
.. _special-functions:
Special Functions
=================
.. index:: ! receive ether function, function;receive ! receive .. index:: ! receive ether function, function;receive ! receive
.. _receive-ether-function: .. _receive-ether-function:
Receive Ether Function Receive Ether Function
====================== ----------------------
A contract can have at most one ``receive`` function, declared using A contract can have at most one ``receive`` function, declared using
``receive() external payable { ... }`` ``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:
Fallback Function Fallback Function
================= -----------------
A contract can have at most one ``fallback`` function, declared using either ``fallback () external [payable]`` 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)`` or ``fallback (bytes calldata _input) external [payable] returns (bytes memory _output)``

View File

@ -303,6 +303,13 @@ contracts can no longer change the behaviour of that function.
outside of interfaces. In interfaces, all functions are outside of interfaces. In interfaces, all functions are
automatically considered ``virtual``. 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 Public state variables can override external functions if the
parameter and return types of the function matches the getter function parameter and return types of the function matches the getter function
of the variable: of the variable:

View File

@ -6,12 +6,14 @@
Interfaces 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. - They cannot inherit from other contracts, but they can inherit from other interfaces.
- All declared functions must be external. - All declared functions must be external.
- They cannot declare a constructor. - They cannot declare a constructor.
- They cannot declare state variables. - They cannot declare state variables.
- They cannot declare modifiers.
Some of these restrictions might be lifted in the future. 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. Contracts can inherit interfaces as they would inherit other contracts.
All functions declared in interfaces are implicitly ``virtual``, which means that All functions declared in interfaces are implicitly ``virtual`` and any
they can be overridden. This does not automatically mean that an overriding function functions that override them do not need the ``override`` keyword.
can be overridden again - this is only possible if the overriding This does not automatically mean that an overriding function can be overridden again -
function is marked ``virtual``. this is only possible if the overriding function is marked ``virtual``.
Interfaces can inherit from other interfaces. This has the same rules as normal Interfaces can inherit from other interfaces. This has the same rules as normal
inheritance. inheritance.

View File

@ -100,12 +100,13 @@ Running the Tests
----------------- -----------------
Solidity includes different types of tests, most of them bundled into the 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. 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, 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``), including those bundled into the `Boost C++ Test Framework <https://www.boost.org/doc/libs/release/libs/test/doc/html/index.html>`_
as well as command line tests and compilation tests. 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 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, 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: 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, - `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/1_69_0/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/run_test.html>`_ to run specific tests cases, and - `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/1_69_0/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/report_level.html>`_ give a more detailed report. - `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 :: .. note ::

View File

@ -65,9 +65,10 @@ uses up at least one stack slot and there are only 1024 slots available.
External Function Calls External Function Calls
----------------------- -----------------------
The expressions ``this.g(8);`` and ``c.g(2);`` (where ``c`` is a contract Functions can also be called using the ``this.g(8);`` and ``c.g(2);`` notation, where
instance) are also valid function calls, but this time, the function ``c`` is a contract instance and ``g`` is a function belonging to ``c``.
will be called "externally", via a message call and not directly via jumps. 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, Please note that function calls on ``this`` cannot be used in the constructor,
as the actual contract has not been created yet. as the actual contract has not been created yet.
@ -105,8 +106,10 @@ otherwise, the ``value`` option would not be available.
.. warning:: .. warning::
Be careful that ``feed.info{value: 10, gas: 800}`` only locally sets the 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 ``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 parentheses at the end perform the actual call. So
function is not called and the ``value`` and ``gas`` settings are lost. ``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 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 always succeed, Solidity uses the ``extcodesize`` opcode to check that

View File

@ -19,6 +19,7 @@ sourceUnit: (
| constantVariableDeclaration | constantVariableDeclaration
| structDefinition | structDefinition
| enumDefinition | enumDefinition
| userDefinedValueTypeDefinition
| errorDefinition | errorDefinition
)* EOF; )* EOF;
@ -89,6 +90,7 @@ contractBodyElement:
| receiveFunctionDefinition | receiveFunctionDefinition
| structDefinition | structDefinition
| enumDefinition | enumDefinition
| userDefinedValueTypeDefinition
| stateVariableDeclaration | stateVariableDeclaration
| eventDefinition | eventDefinition
| errorDefinition | 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. * 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; 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. * The declaration of a state variable.

View File

@ -313,7 +313,8 @@ The following are dependencies for all builds of Solidity:
+===================================+=======================================================+ +===================================+=======================================================+
| `CMake`_ (version 3.13+) | Cross-platform build file generator. | | `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. | | `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. 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 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. | | `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, If you already have one IDE and only need the compiler and libraries,
you could install Visual Studio 2019 Build Tools. you could install Visual Studio 2019 Build Tools.

View File

@ -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 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 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. canonical form and, if possible, making it relative to either the base path or one of the
See :ref:`Base Path Normalization and Stripping <base-path-normalization-and-stripping>` for include paths.
See :ref:`CLI Path Normalization and Stripping <cli-path-normalization-and-stripping>` for
a detailed description of this process. a detailed description of this process.
.. index:: standard JSON .. 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 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 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 .. index:: ! base path, ! --base-path, ! include paths, ! --include-path
.. _base-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. The base path and include paths represent directories that the Host Filesystem Loader will load files from.
It is simply prepended to a source unit name before the filesystem lookup is performed. 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. 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 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 If the base path itself is relative, it is interpreted as relative to the current working directory
of the compiler. 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: 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 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 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. 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 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: 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 You can avoid such situations by ensuring that all the files are available within a single
directory tree on the same drive. 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 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). 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 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 ``../``. This makes the conversion unambiguous and ensures that the relative path does not start with ``../``.
The resulting file path becomes the source unit name. 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:: .. note::
Prior to version 0.8.8, CLI path stripping was not performed and the only normalization applied 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 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. 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 .. index:: ! remapping; import, ! import; remapping, ! remapping; context, ! remapping; prefix, ! remapping; target
.. _import-remapping: .. _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 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. 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 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: you checked out to ``/project/dapp-bin_old``, then you can run:

View File

@ -353,6 +353,10 @@ code.
Always use the latest version of the compiler to be notified about all recently Always use the latest version of the compiler to be notified about all recently
introduced warnings. 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 Restrict the Amount of Ether
============================ ============================

View File

@ -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: following operators are available as shorthands:
``a += e`` is equivalent to ``a = a + e``. The operators ``-=``, ``*=``, ``/=``, ``%=``, ``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 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 of ``a``. In contrast, ``--a`` and ``++a`` have the same effect on ``a`` but
return the value after the change. return the value after the change.

View File

@ -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 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 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. 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 The data representation is the same as for enums in C: The options are represented by
subsequent unsigned integer values starting from ``0``. 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 .. code-block:: solidity
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0; pragma solidity ^0.8.8;
contract test { contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
@ -612,11 +615,83 @@ subsequent unsigned integer values starting from ``0``.
function getDefaultChoice() public pure returns (uint) { function getDefaultChoice() public pure returns (uint) {
return uint(defaultChoice); return uint(defaultChoice);
} }
function getLargestValue() public pure returns (ActionChoices) {
return type(ActionChoices).max;
}
function getSmallestValue() public pure returns (ActionChoices) {
return type(ActionChoices).min;
}
} }
.. note:: .. note::
Enums can also be declared on the file level, outside of contract or library definitions. 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 .. index:: ! function type, ! type; function

View File

@ -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 size of the binary search in the function dispatch routine
- the way constants like large numbers or strings are stored - 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 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 This essentially instructs the compiler to search for anything starting with
``github.com/ethereum/dapp-bin/`` under ``/usr/local/lib/dapp-bin``. ``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 ./ 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 or ../ <direct-imports>` are treated as relative to the directories specified using
``--base-path`` option (or the current working directory if base path is not specified). ``--base-path`` and ``--include-path`` options (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. 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 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 remappings are automatically allowed to be accessed by the file reader, but everything
else is rejected by default. else is rejected by default.
@ -437,7 +434,7 @@ Output Description
.. code-block:: javascript .. code-block:: javascript
{ {
// Optional: not present if no errors/warnings were encountered // Optional: not present if no errors/warnings/infos were encountered
"errors": [ "errors": [
{ {
// Optional: Location within the source file. // Optional: Location within the source file.
@ -460,7 +457,7 @@ Output Description
"type": "TypeError", "type": "TypeError",
// Mandatory: Component where the error originated, such as "general", "ewasm", etc. // Mandatory: Component where the error originated, such as "general", "ewasm", etc.
"component": "general", "component": "general",
// Mandatory ("error" or "warning") // Mandatory ("error", "warning" or "info", but please note that this may be extended in the future)
"severity": "error", "severity": "error",
// Optional: unique code for the cause of the error // Optional: unique code for the cause of the error
"errorCode": "3141", "errorCode": "3141",
@ -604,6 +601,7 @@ Error Types
11. ``CompilerError``: Invalid use of the compiler stack - this should be reported as an issue. 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. 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. 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: .. _compiler-tools:

View File

@ -174,9 +174,32 @@ whitespace, i.e. there is no terminating ``;`` or newline required.
Literals Literals
-------- --------
As literals, you can use integer constants in decimal or hexadecimal notation As literals, you can use:
or strings as ASCII (`"abc"`) or HEX strings (`hex"616263"`) of up to
32 bytes length. - 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 When compiling for the EVM, this will be translated into an
appropriate ``PUSHi`` instruction. In the following example, 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. bitwise ``and`` with the string "abc" is computed.
The final value is assigned to a local variable called ``x``. The final value is assigned to a local variable called ``x``.
Strings are stored left-aligned and cannot be longer than 32 bytes. The 32-byte limit above does not apply to string literals passed to builtin functions that require
The limit 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 literal arguments (e.g. ``setimmutable`` or ``loadimmutable``). Those strings never end up in the
generated bytecode. generated bytecode.

View File

@ -31,12 +31,16 @@
#include <libevmasm/ConstantOptimiser.h> #include <libevmasm/ConstantOptimiser.h>
#include <libevmasm/GasMeter.h> #include <libevmasm/GasMeter.h>
#include <liblangutil/CharStream.h>
#include <liblangutil/Exceptions.h> #include <liblangutil/Exceptions.h>
#include <json/json.h> #include <json/json.h>
#include <fstream>
#include <range/v3/algorithm/any_of.hpp> #include <range/v3/algorithm/any_of.hpp>
#include <range/v3/view/enumerate.hpp>
#include <fstream>
#include <limits>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;
@ -55,7 +59,7 @@ AssemblyItem const& Assembly::append(AssemblyItem const& _i)
return m_items.back(); return m_items.back();
} }
unsigned Assembly::bytesRequired(unsigned subTagSize) const unsigned Assembly::codeSize(unsigned subTagSize) const
{ {
for (unsigned tagSize = subTagSize; true; ++tagSize) for (unsigned tagSize = subTagSize; true; ++tagSize)
{ {
@ -65,7 +69,7 @@ unsigned Assembly::bytesRequired(unsigned subTagSize) const
for (AssemblyItem const& i: m_items) for (AssemblyItem const& i: m_items)
ret += i.bytesRequired(tagSize); ret += i.bytesRequired(tagSize);
if (util::bytesRequired(ret) <= tagSize) if (numberEncodingSize(ret) <= tagSize)
return static_cast<unsigned>(ret); return static_cast<unsigned>(ret);
} }
} }
@ -82,16 +86,7 @@ string locationFromSources(StringMap const& _sourceCodes, SourceLocation const&
if (it == _sourceCodes.end()) if (it == _sourceCodes.end())
return {}; return {};
string const& source = it->second; return CharStream::singleLineSnippet(it->second, _location);
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;
} }
class Functionalizer class Functionalizer
@ -185,7 +180,7 @@ void Assembly::assemblyStream(ostream& _out, string const& _prefix, StringMap co
_out << _prefix << "stop" << endl; _out << _prefix << "stop" << endl;
for (auto const& i: m_data) for (auto const& i: m_data)
if (u256(i.first) >= m_subs.size()) 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) 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) 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 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 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["name"] = _name;
value["source"] = _source; value["source"] = _source;
value["begin"] = _begin; value["begin"] = _begin;
@ -230,8 +225,9 @@ string Assembly::toStringInHex(u256 _value)
Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices) const Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices) const
{ {
Json::Value root; 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) for (AssemblyItem const& i: m_items)
{ {
int sourceIndex = -1; int sourceIndex = -1;
@ -258,10 +254,6 @@ Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices)
collection.append( collection.append(
createJsonValue("PUSH", sourceIndex, i.location().start, i.location().end, toStringInHex(i.data()), i.getJumpTypeAsString())); createJsonValue("PUSH", sourceIndex, i.location().start, i.location().end, toStringInHex(i.data()), i.getJumpTypeAsString()));
break; break;
case PushString:
collection.append(
createJsonValue("PUSH tag", sourceIndex, i.location().start, i.location().end, m_strings.at(h256(i.data()))));
break;
case PushTag: case PushTag:
if (i.data() == 0) if (i.data() == 0)
collection.append( 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()))); collection.append(createJsonValue("PUSH data", sourceIndex, i.location().start, i.location().end, toStringInHex(i.data())));
break; break;
case VerbatimBytecode: 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; break;
default: default:
assertThrow(false, InvalidOpcode, ""); assertThrow(false, InvalidOpcode, "");
@ -329,10 +321,11 @@ Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices)
if (!m_data.empty() || !m_subs.empty()) 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) for (auto const& i: m_data)
if (u256(i.first) >= m_subs.size()) 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) 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) if (m_auxiliaryData.size() > 0)
root[".auxdata"] = toHex(m_auxiliaryData); root[".auxdata"] = util::toHex(m_auxiliaryData);
return root; return root;
} }
@ -409,18 +402,21 @@ Assembly& Assembly::optimise(OptimiserSettings const& _settings)
return *this; return *this;
} }
map<u256, u256> Assembly::optimiseInternal( map<u256, u256> const& Assembly::optimiseInternal(
OptimiserSettings const& _settings, OptimiserSettings const& _settings,
std::set<size_t> _tagsReferencedFromOutside std::set<size_t> _tagsReferencedFromOutside
) )
{ {
if (m_tagReplacements)
return *m_tagReplacements;
// Run optimisation for sub-assemblies. // Run optimisation for sub-assemblies.
for (size_t subId = 0; subId < m_subs.size(); ++subId) for (size_t subId = 0; subId < m_subs.size(); ++subId)
{ {
OptimiserSettings settings = _settings; OptimiserSettings settings = _settings;
// Disable creation mode for sub-assemblies. // Disable creation mode for sub-assemblies.
settings.isCreation = false; settings.isCreation = false;
map<u256, u256> subTagReplacements = m_subs[subId]->optimiseInternal( map<u256, u256> const& subTagReplacements = m_subs[subId]->optimiseInternal(
settings, settings,
JumpdestRemover::referencedTags(m_items, subId) JumpdestRemover::referencedTags(m_items, subId)
); );
@ -546,7 +542,8 @@ map<u256, u256> Assembly::optimiseInternal(
*this *this
); );
return tagReplacements; m_tagReplacements = move(tagReplacements);
return *m_tagReplacements;
} }
LinkerObject const& Assembly::assemble() const LinkerObject const& Assembly::assemble() const
@ -597,20 +594,20 @@ LinkerObject const& Assembly::assemble() const
"Cannot push and assign immutables in the same assembly subroutine." "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()); m_tagPositionsInBytecode = vector<size_t>(m_usedTags, numeric_limits<size_t>::max());
map<size_t, pair<size_t, size_t>> tagRef; map<size_t, pair<size_t, size_t>> tagRef;
multimap<h256, unsigned> dataRef; multimap<h256, unsigned> dataRef;
multimap<size_t, size_t> subRef; multimap<size_t, size_t> subRef;
vector<unsigned> sizeRef; ///< Pointers to code locations where the size of the program is inserted 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)); uint8_t tagPush = static_cast<uint8_t>(pushInstruction(bytesPerTag));
unsigned bytesRequiredIncludingData = bytesRequiredForCode + 1 + static_cast<unsigned>(m_auxiliaryData.size()); unsigned bytesRequiredIncludingData = bytesRequiredForCode + 1 + static_cast<unsigned>(m_auxiliaryData.size());
for (auto const& sub: m_subs) for (auto const& sub: m_subs)
bytesRequiredIncludingData += static_cast<unsigned>(sub->assemble().bytecode.size()); 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)); uint8_t dataRefPush = static_cast<uint8_t>(pushInstruction(bytesPerDataRef));
ret.bytecode.reserve(bytesRequiredIncludingData); ret.bytecode.reserve(bytesRequiredIncludingData);
@ -625,22 +622,9 @@ LinkerObject const& Assembly::assemble() const
case Operation: case Operation:
ret.bytecode.push_back(static_cast<uint8_t>(i.instruction())); ret.bytecode.push_back(static_cast<uint8_t>(i.instruction()));
break; 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: 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.push_back(static_cast<uint8_t>(pushInstruction(b)));
ret.bytecode.resize(ret.bytecode.size() + b); ret.bytecode.resize(ret.bytecode.size() + b);
bytesRef byr(&ret.bytecode.back() + 1 - b, 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, ""); assertThrow(i.data() <= numeric_limits<size_t>::max(), AssemblyException, "");
auto s = subAssemblyById(static_cast<size_t>(i.data()))->assemble().bytecode.size(); auto s = subAssemblyById(static_cast<size_t>(i.data()))->assemble().bytecode.size();
i.setPushedValue(u256(s)); 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.push_back(static_cast<uint8_t>(pushInstruction(b)));
ret.bytecode.resize(ret.bytecode.size() + b); ret.bytecode.resize(ret.bytecode.size() + b);
bytesRef byr(&ret.bytecode.back() + 1 - b, 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."); assertThrow(tagId < tagPositions.size(), AssemblyException, "Reference to non-existing tag.");
size_t pos = tagPositions[tagId]; size_t pos = tagPositions[tagId];
assertThrow(pos != numeric_limits<size_t>::max(), AssemblyException, "Reference to tag without position."); 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); bytesRef r(ret.bytecode.data() + i.first, bytesPerTag);
toBigEndian(pos, r); toBigEndian(pos, r);
} }
for (auto const& [name, tagInfo]: m_namedTags) for (auto const& [name, tagInfo]: m_namedTags)
{ {
size_t position = m_tagPositionsInBytecode.at(tagInfo.id); 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] = { ret.functionDebugData[name] = {
position == numeric_limits<size_t>::max() ? nullopt : optional<size_t>{position}, position == numeric_limits<size_t>::max() ? nullopt : optional<size_t>{position},
tagIndex,
tagInfo.sourceID, tagInfo.sourceID,
tagInfo.params, tagInfo.params,
tagInfo.returns tagInfo.returns

View File

@ -165,9 +165,9 @@ protected:
/// Does the same operations as @a optimise, but should only be applied to a sub and /// 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 /// returns the replaced tags. Also takes an argument containing the tags of this assembly
/// that are referenced in a super-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: private:
static Json::Value createJsonValue( 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). /// 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; 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 LinkerObject m_assembledObject;
mutable std::vector<size_t> m_tagPositionsInBytecode; mutable std::vector<size_t> m_tagPositionsInBytecode;

View File

@ -21,11 +21,13 @@
#include <libevmasm/Assembly.h> #include <libevmasm/Assembly.h>
#include <libsolutil/CommonData.h> #include <libsolutil/CommonData.h>
#include <libsolutil/Numeric.h>
#include <libsolutil/StringUtils.h> #include <libsolutil/StringUtils.h>
#include <libsolutil/FixedHash.h> #include <libsolutil/FixedHash.h>
#include <liblangutil/SourceLocation.h> #include <liblangutil/SourceLocation.h>
#include <fstream> #include <fstream>
#include <limits>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;
@ -70,10 +72,8 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength) const
case Operation: case Operation:
case Tag: // 1 byte for the JUMPDEST case Tag: // 1 byte for the JUMPDEST
return 1; return 1;
case PushString:
return 1 + 32;
case Push: case Push:
return 1 + max<size_t>(1, util::bytesRequired(data())); return 1 + max<size_t>(1, numberEncodingSize(data()));
case PushSubSize: case PushSubSize:
case PushProgramSize: case PushProgramSize:
return 1 + 4; // worst case: a 16MB program return 1 + 4; // worst case: a 16MB program
@ -118,7 +118,6 @@ size_t AssemblyItem::returnValues() const
case Operation: case Operation:
return static_cast<size_t>(instructionInfo(instruction()).ret); return static_cast<size_t>(instructionInfo(instruction()).ret);
case Push: case Push:
case PushString:
case PushTag: case PushTag:
case PushData: case PushData:
case PushSub: case PushSub:
@ -147,7 +146,6 @@ bool AssemblyItem::canBeFunctional() const
case Operation: case Operation:
return !isDupInstruction(instruction()) && !isSwapInstruction(instruction()); return !isDupInstruction(instruction()) && !isSwapInstruction(instruction());
case Push: case Push:
case PushString:
case PushTag: case PushTag:
case PushData: case PushData:
case PushSub: case PushSub:
@ -193,10 +191,7 @@ string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
break; break;
} }
case Push: case Push:
text = toHex(util::toCompactBigEndian(data(), 1), util::HexPrefix::Add); text = toHex(toCompactBigEndian(data(), 1), util::HexPrefix::Add);
break;
case PushString:
text = string("data_") + util::toHex(data());
break; break;
case PushTag: case PushTag:
{ {
@ -214,7 +209,7 @@ string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
text = string("tag_") + to_string(static_cast<size_t>(data())) + ":"; text = string("tag_") + to_string(static_cast<size_t>(data())) + ":";
break; break;
case PushData: case PushData:
text = string("data_") + util::toHex(data()); text = string("data_") + toHex(data());
break; break;
case PushSub: case PushSub:
case PushSubSize: case PushSubSize:
@ -233,16 +228,16 @@ string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
text = string("bytecodeSize"); text = string("bytecodeSize");
break; break;
case PushLibraryAddress: case PushLibraryAddress:
text = string("linkerSymbol(\"") + util::toHex(data()) + string("\")"); text = string("linkerSymbol(\"") + toHex(data()) + string("\")");
break; break;
case PushDeployTimeAddress: case PushDeployTimeAddress:
text = string("deployTimeAddress()"); text = string("deployTimeAddress()");
break; break;
case PushImmutable: case PushImmutable:
text = string("immutable(\"") + toHex(util::toCompactBigEndian(data(), 1), util::HexPrefix::Add) + "\")"; text = string("immutable(\"") + "0x" + util::toHex(toCompactBigEndian(data(), 1)) + "\")";
break; break;
case AssignImmutable: case AssignImmutable:
text = string("assignImmutable(\"") + toHex(util::toCompactBigEndian(data(), 1), util::HexPrefix::Add) + "\")"; text = string("assignImmutable(\"") + "0x" + util::toHex(toCompactBigEndian(data(), 1)) + "\")";
break; break;
case UndefinedItem: case UndefinedItem:
assertThrow(false, AssemblyException, "Invalid assembly item."); assertThrow(false, AssemblyException, "Invalid assembly item.");
@ -276,9 +271,6 @@ ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item)
case Push: case Push:
_out << " PUSH " << hex << _item.data() << dec; _out << " PUSH " << hex << _item.data() << dec;
break; break;
case PushString:
_out << " PushString" << hex << (unsigned)_item.data() << dec;
break;
case PushTag: case PushTag:
{ {
size_t subId = _item.splitForeignPushTag().first; size_t subId = _item.splitForeignPushTag().first;

View File

@ -38,7 +38,6 @@ enum AssemblyItemType
UndefinedItem, UndefinedItem,
Operation, Operation,
Push, Push,
PushString,
PushTag, PushTag,
PushSub, PushSub,
PushSubSize, PushSubSize,

View File

@ -25,6 +25,8 @@
#pragma once #pragma once
#include <libsolutil/Common.h> #include <libsolutil/Common.h>
#include <libsolutil/Numeric.h>
#include <cstddef> #include <cstddef>
#include <vector> #include <vector>
@ -64,9 +66,14 @@ private:
/// Iterator that skips tags and skips to the end if (all branches of) the control /// Iterator that skips tags and skips to the end if (all branches of) the control
/// flow does not continue to the next instruction. /// flow does not continue to the next instruction.
/// If the arguments are supplied to the constructor, replaces items on the fly. /// 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: 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( BlockIterator(
AssemblyItems::const_iterator _it, AssemblyItems::const_iterator _it,
AssemblyItems::const_iterator _end, AssemblyItems::const_iterator _end,

View File

@ -133,7 +133,7 @@ bigint LiteralMethod::gasNeeded() const
return combineGas( return combineGas(
simpleRunGas({Instruction::PUSH1}), simpleRunGas({Instruction::PUSH1}),
// PUSHX plus data // 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 0
); );
} }
@ -146,13 +146,13 @@ bigint CodeCopyMethod::gasNeeded() const
// Data gas for copy routines: Some bytes are zero, but we ignore them. // 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), bytesRequired(copyRoutine()) * (m_params.isCreation ? GasCosts::txDataNonZeroGas(m_params.evmVersion) : GasCosts::createDataGas),
// Data gas for data itself // Data gas for data itself
dataGas(util::toBigEndian(m_value)) dataGas(toBigEndian(m_value))
); );
} }
AssemblyItems CodeCopyMethod::execute(Assembly& _assembly) const 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."); assertThrow(data.size() == 32, OptimizerException, "Invalid number encoding.");
AssemblyItems actualCopyRoutine = copyRoutine(); AssemblyItems actualCopyRoutine = copyRoutine();
actualCopyRoutine[4] = _assembly.newData(data); actualCopyRoutine[4] = _assembly.newData(data);
@ -192,7 +192,7 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
if (_value < 0x10000) if (_value < 0x10000)
// Very small value, not worth computing // Very small value, not worth computing
return AssemblyItems{_value}; return AssemblyItems{_value};
else if (util::bytesRequired(~_value) < util::bytesRequired(_value)) else if (numberEncodingSize(~_value) < numberEncodingSize(_value))
// Negated is shorter to represent // Negated is shorter to represent
return findRepresentation(~_value) + AssemblyItems{Instruction::NOT}; return findRepresentation(~_value) + AssemblyItems{Instruction::NOT};
else else

View File

@ -24,12 +24,14 @@
#pragma once #pragma once
#include <vector>
#include <memory>
#include <libsolutil/Common.h> #include <libsolutil/Common.h>
#include <libsolutil/Assertions.h> #include <libsolutil/Assertions.h>
#include <libevmasm/ExpressionClasses.h> #include <libevmasm/ExpressionClasses.h>
#include <vector>
#include <memory>
#include <limits>
namespace solidity::evmasm namespace solidity::evmasm
{ {

View File

@ -29,6 +29,7 @@
#include <functional> #include <functional>
#include <tuple> #include <tuple>
#include <limits>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;

View File

@ -47,7 +47,6 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
case Push: case Push:
case PushTag: case PushTag:
case PushData: case PushData:
case PushString:
case PushSub: case PushSub:
case PushSubSize: case PushSubSize:
case PushProgramSize: case PushProgramSize:

View File

@ -36,7 +36,7 @@
#include <range/v3/view/transform.hpp> #include <range/v3/view/transform.hpp>
#include <optional> #include <optional>
#include <limits>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;

View File

@ -25,6 +25,7 @@
#include <libevmasm/Exceptions.h> #include <libevmasm/Exceptions.h>
#include <libsolutil/Common.h> #include <libsolutil/Common.h>
#include <libsolutil/Assertions.h> #include <libsolutil/Assertions.h>
#include <libsolutil/Numeric.h>
#include <functional> #include <functional>
namespace solidity::evmasm namespace solidity::evmasm
@ -301,7 +302,7 @@ bool isValidInstruction(Instruction _inst);
extern const std::map<std::string, Instruction> c_instructions; extern const std::map<std::string, Instruction> c_instructions;
/// Iterate through EVM code and call a function on each instruction. /// 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. /// Convert from EVM code to simple EVM assembly language.
std::string disassemble(bytes const& _mem, std::string const& _delimiter = " "); std::string disassemble(bytes const& _mem, std::string const& _delimiter = " ");

View File

@ -24,6 +24,8 @@
#include <libevmasm/AssemblyItem.h> #include <libevmasm/AssemblyItem.h>
#include <limits>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;
using namespace solidity::util; using namespace solidity::util;

View File

@ -415,7 +415,7 @@ KnownState::Id KnownState::applyKeccak256(
{ {
bytes data; bytes data;
for (Id a: arguments) for (Id a: arguments)
data += util::toBigEndian(*m_expressionClasses->knownConstant(a)); data += toBigEndian(*m_expressionClasses->knownConstant(a));
data.resize(length); data.resize(length);
v = m_expressionClasses->find(AssemblyItem(u256(util::keccak256(data)), _location)); v = m_expressionClasses->find(AssemblyItem(u256(util::keccak256(data)), _location));
} }

View File

@ -24,14 +24,6 @@
#pragma once #pragma once
#include <utility>
#include <vector>
#include <map>
#include <set>
#include <tuple>
#include <memory>
#include <ostream>
#if defined(__clang__) #if defined(__clang__)
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wredeclared-class-member" #pragma clang diagnostic ignored "-Wredeclared-class-member"
@ -58,6 +50,15 @@
#include <libevmasm/ExpressionClasses.h> #include <libevmasm/ExpressionClasses.h>
#include <libevmasm/SemanticInformation.h> #include <libevmasm/SemanticInformation.h>
#include <limits>
#include <utility>
#include <vector>
#include <map>
#include <set>
#include <tuple>
#include <memory>
#include <ostream>
namespace solidity::langutil namespace solidity::langutil
{ {
struct SourceLocation; struct SourceLocation;

View File

@ -48,6 +48,7 @@ struct LinkerObject
struct FunctionDebugData struct FunctionDebugData
{ {
std::optional<size_t> bytecodeOffset; std::optional<size_t> bytecodeOffset;
std::optional<size_t> instructionIndex;
std::optional<size_t> sourceID; std::optional<size_t> sourceID;
size_t params = {}; size_t params = {};
size_t returns = {}; size_t returns = {};

View File

@ -112,7 +112,7 @@ struct PushPop: SimplePeepholeOptimizerMethod<PushPop, 2>
auto t = _push.type(); auto t = _push.type();
return _pop == Instruction::POP && ( return _pop == Instruction::POP && (
SemanticInformation::isDupInstruction(_push) || 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 t == PushSubSize || t == PushProgramSize || t == PushData || t == PushLibraryAddress
); );
} }

View File

@ -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. // 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) template <class S> S shlWorkaround(S const& _x, unsigned _amount)
{ {
return u256((bigint(_x) << _amount) & u256(-1)); return u256((bigint(_x) << _amount) & u256(-1));

View File

@ -41,7 +41,6 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool
case VerbatimBytecode: case VerbatimBytecode:
return true; return true;
case Push: case Push:
case PushString:
case PushTag: case PushTag:
case PushSub: case PushSub:
case PushSubSize: case PushSubSize:

View File

@ -128,3 +128,19 @@ string_view CharStream::text(SourceLocation const& _location) const
static_cast<size_t>(_location.end - _location.start) 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;
}

View File

@ -118,6 +118,15 @@ public:
/// Returns an empty string view if the source location does not `hasText()`. /// Returns an empty string view if the source location does not `hasText()`.
std::string_view text(SourceLocation const& _location) const; 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: private:
std::string m_source; std::string m_source;
std::string m_name; std::string m_name;

View File

@ -94,6 +94,16 @@ bool ErrorReporter::checkForExcessiveErrors(Error::Type _type)
if (m_warningCount >= c_maxWarningsAllowed) if (m_warningCount >= c_maxWarningsAllowed)
return true; 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 else
{ {
m_errorCount++; m_errorCount++;
@ -242,3 +252,12 @@ void ErrorReporter::docstringParsingError(ErrorId _error, SourceLocation const&
_description _description
); );
} }
void ErrorReporter::info(
ErrorId _error,
SourceLocation const& _location,
string const& _description
)
{
error(_error, Error::Type::Info, _location, _description);
}

View File

@ -63,6 +63,8 @@ public:
SecondarySourceLocation const& _secondaryLocation SecondarySourceLocation const& _secondaryLocation
); );
void info(ErrorId _error, SourceLocation const& _location, std::string const& _description);
void error( void error(
ErrorId _error, ErrorId _error,
Error::Type _type, Error::Type _type,
@ -118,13 +120,13 @@ public:
void clear(); 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 bool hasErrors() const
{ {
return m_errorCount > 0; return m_errorCount > 0;
} }
/// @returns the number of errors (ignores warnings). /// @returns the number of errors (ignores warnings and infos).
unsigned errorCount() const unsigned errorCount() const
{ {
return m_errorCount; return m_errorCount;
@ -183,9 +185,11 @@ private:
unsigned m_errorCount = 0; unsigned m_errorCount = 0;
unsigned m_warningCount = 0; unsigned m_warningCount = 0;
unsigned m_infoCount = 0;
unsigned const c_maxWarningsAllowed = 256; unsigned const c_maxWarningsAllowed = 256;
unsigned const c_maxErrorsAllowed = 256; unsigned const c_maxErrorsAllowed = 256;
unsigned const c_maxInfosAllowed = 256;
}; };
} }

View File

@ -47,6 +47,9 @@ Error::Error(
case Type::DocstringParsingError: case Type::DocstringParsingError:
m_typeName = "DocstringParsingError"; m_typeName = "DocstringParsingError";
break; break;
case Type::Info:
m_typeName = "Info";
break;
case Type::ParserError: case Type::ParserError:
m_typeName = "ParserError"; m_typeName = "ParserError";
break; break;

View File

@ -28,6 +28,10 @@
#include <libsolutil/CommonData.h> #include <libsolutil/CommonData.h>
#include <liblangutil/SourceLocation.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 <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -45,18 +49,68 @@ struct FatalError: virtual util::Exception {};
struct UnimplementedFeatureError: virtual util::Exception {}; struct UnimplementedFeatureError: virtual util::Exception {};
struct InvalidAstError: virtual util::Exception {}; struct InvalidAstError: virtual util::Exception {};
/// Assertion that throws an InternalCompilerError containing the given description if it is not met. /// Assertion that throws an InternalCompilerError containing the given description if it is not met.
#define solAssert(CONDITION, DESCRIPTION) \ #if !BOOST_PP_VARIADICS_MSVC
assertThrow(CONDITION, ::solidity::langutil::InternalCompilerError, DESCRIPTION) #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) \ #define solAssert_1(CONDITION) \
assertThrow(CONDITION, ::solidity::langutil::UnimplementedFeatureError, DESCRIPTION) 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) \ #define solUnimplemented(DESCRIPTION) \
solUnimplementedAssert(false, 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>; using errorSourceLocationInfo = std::pair<std::string, SourceLocation>;
@ -120,7 +174,15 @@ public:
ParserError, ParserError,
TypeError, TypeError,
SyntaxError, SyntaxError,
Warning Warning,
Info
};
enum class Severity
{
Error,
Warning,
Info
}; };
Error( Error(
@ -139,21 +201,63 @@ public:
static Error const* containsErrorOfType(ErrorList const& _list, Error::Type _type) static Error const* containsErrorOfType(ErrorList const& _list, Error::Type _type)
{ {
for (auto e: _list) for (auto e: _list)
{
if (e->type() == _type) if (e->type() == _type)
return e.get(); return e.get();
}
return nullptr; 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) for (auto e: _list)
{ if (isError(e->type()))
if (e->type() != Type::Warning) return true;
return false; 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: private:
ErrorId m_errorId; ErrorId m_errorId;
Type m_type; Type m_type;

View File

@ -104,7 +104,7 @@ protected:
void fatalParserError(ErrorId _error, SourceLocation const& _location, std::string const& _description); void fatalParserError(ErrorId _error, SourceLocation const& _location, std::string const& _description);
std::shared_ptr<Scanner> m_scanner; 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; ErrorReporter& m_errorReporter;
/// Current recursion depth during parsing. /// Current recursion depth during parsing.
size_t m_recursionDepth = 0; size_t m_recursionDepth = 0;

View File

@ -59,6 +59,7 @@
#include <optional> #include <optional>
#include <string_view> #include <string_view>
#include <tuple> #include <tuple>
#include <array>
using namespace std; using namespace std;

View File

@ -26,6 +26,7 @@
#include <liblangutil/Exceptions.h> #include <liblangutil/Exceptions.h>
#include <functional> #include <functional>
#include <limits>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;

View File

@ -30,7 +30,7 @@ using namespace solidity::langutil;
SourceReferenceExtractor::Message SourceReferenceExtractor::extract( SourceReferenceExtractor::Message SourceReferenceExtractor::extract(
CharStreamProvider const& _charStreamProvider, CharStreamProvider const& _charStreamProvider,
util::Exception const& _exception, util::Exception const& _exception,
string _category string _severity
) )
{ {
SourceLocation const* location = boost::get_error_info<errinfo_sourceLocation>(_exception); SourceLocation const* location = boost::get_error_info<errinfo_sourceLocation>(_exception);
@ -44,7 +44,7 @@ SourceReferenceExtractor::Message SourceReferenceExtractor::extract(
for (auto const& info: secondaryLocation->infos) for (auto const& info: secondaryLocation->infos)
secondary.emplace_back(extract(_charStreamProvider, &info.second, info.first)); 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( SourceReferenceExtractor::Message SourceReferenceExtractor::extract(
@ -52,8 +52,8 @@ SourceReferenceExtractor::Message SourceReferenceExtractor::extract(
Error const& _error Error const& _error
) )
{ {
string category = (_error.type() == Error::Type::Warning) ? "Warning" : "Error"; string severity = Error::formatErrorSeverity(Error::errorSeverity(_error.type()));
Message message = extract(_charStreamProvider, _error, category); Message message = extract(_charStreamProvider, _error, severity);
message.errorId = _error.errorId(); message.errorId = _error.errorId();
return message; return message;
} }

View File

@ -41,7 +41,7 @@ struct LineColumn
struct SourceReference 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). std::string sourceName; ///< Underlying source name (for example the filename).
LineColumn position; ///< Actual (error) position this source reference is surrounding. LineColumn position; ///< Actual (error) position this source reference is surrounding.
bool multiline = {false}; ///< Indicates whether the actual SourceReference is truncated to one line. bool multiline = {false}; ///< Indicates whether the actual SourceReference is truncated to one line.
@ -64,12 +64,12 @@ namespace SourceReferenceExtractor
struct Message struct Message
{ {
SourceReference primary; SourceReference primary;
std::string category; // "Error", "Warning", ... std::string severity; // "Error", "Warning", "Info", ...
std::vector<SourceReference> secondary; std::vector<SourceReference> secondary;
std::optional<ErrorId> errorId; 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); Message extract(CharStreamProvider const& _charStreamProvider, Error const& _error);
SourceReference extract(CharStreamProvider const& _charStreamProvider, SourceLocation const* _location, std::string message = ""); SourceReference extract(CharStreamProvider const& _charStreamProvider, SourceLocation const* _location, std::string message = "");
} }

View File

@ -164,7 +164,7 @@ void SourceReferenceFormatter::printSourceLocation(SourceReference const& _ref)
void SourceReferenceFormatter::printExceptionInformation(SourceReferenceExtractor::Message const& _msg) void SourceReferenceFormatter::printExceptionInformation(SourceReferenceExtractor::Message const& _msg)
{ {
// exception header line // exception header line
errorColored() << _msg.category; errorColored() << _msg.severity;
if (m_withErrorIds && _msg.errorId.has_value()) if (m_withErrorIds && _msg.errorId.has_value())
errorColored() << " (" << _msg.errorId.value().error << ")"; errorColored() << " (" << _msg.errorId.value().error << ")";
messageColored() << ": " << _msg.primary.message << '\n'; messageColored() << ": " << _msg.primary.message << '\n';
@ -181,9 +181,9 @@ void SourceReferenceFormatter::printExceptionInformation(SourceReferenceExtracto
m_stream << '\n'; 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) void SourceReferenceFormatter::printErrorInformation(ErrorList const& _errors)

View File

@ -52,7 +52,7 @@ public:
/// Prints source location if it is given. /// Prints source location if it is given.
void printSourceLocation(SourceReference const& _ref); void printSourceLocation(SourceReference const& _ref);
void printExceptionInformation(SourceReferenceExtractor::Message const& _msg); 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(langutil::ErrorList const& _errors);
void printErrorInformation(Error const& _error); void printErrorInformation(Error const& _error);
@ -77,7 +77,7 @@ public:
{ {
return formatExceptionInformation( return formatExceptionInformation(
_error, _error,
(_error.type() == Error::Type::Warning) ? "Warning" : "Error", Error::formatErrorSeverity(Error::errorSeverity(_error.type())),
_charStreamProvider _charStreamProvider
); );
} }

View File

@ -58,11 +58,7 @@ public:
void warning(ErrorId _error, std::string const& _description) void warning(ErrorId _error, std::string const& _description)
{ {
if (!seen(_error, {}, _description)) m_errorReporter.warning(_error, _description);
{
m_errorReporter.warning(_error, _description);
markAsSeen(_error, {}, _description);
}
} }
bool seen(ErrorId _error, SourceLocation const& _location, std::string const& _description) const bool seen(ErrorId _error, SourceLocation const& _location, std::string const& _description) const
@ -77,8 +73,8 @@ public:
void markAsSeen(ErrorId _error, SourceLocation const& _location, std::string const& _description) void markAsSeen(ErrorId _error, SourceLocation const& _location, std::string const& _description)
{ {
solAssert(!seen(_error, _location, _description), ""); if (_location != SourceLocation{})
m_seenErrors[{_error, _location}] = _description; m_seenErrors[{_error, _location}] = _description;
} }
ErrorList const& errors() const { return m_errorReporter.errors(); } ErrorList const& errors() const { return m_errorReporter.errors(); }

View File

@ -60,8 +60,8 @@ void CHCSmtLib2Interface::reset()
void CHCSmtLib2Interface::registerRelation(Expression const& _expr) void CHCSmtLib2Interface::registerRelation(Expression const& _expr)
{ {
smtAssert(_expr.sort, ""); smtAssert(_expr.sort);
smtAssert(_expr.sort->kind == Kind::Function, ""); smtAssert(_expr.sort->kind == Kind::Function);
if (!m_variables.count(_expr.name)) if (!m_variables.count(_expr.name))
{ {
auto fSort = dynamic_pointer_cast<FunctionSort>(_expr.sort); 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) void CHCSmtLib2Interface::declareVariable(string const& _name, SortPointer const& _sort)
{ {
smtAssert(_sort, ""); smtAssert(_sort);
if (_sort->kind == Kind::Function) if (_sort->kind == Kind::Function)
declareFunction(_name, _sort); declareFunction(_name, _sort);
else if (!m_variables.count(_name)) else if (!m_variables.count(_name))
@ -172,13 +172,13 @@ string CHCSmtLib2Interface::forall()
void CHCSmtLib2Interface::declareFunction(string const& _name, SortPointer const& _sort) void CHCSmtLib2Interface::declareFunction(string const& _name, SortPointer const& _sort)
{ {
smtAssert(_sort, ""); smtAssert(_sort);
smtAssert(_sort->kind == Kind::Function, ""); smtAssert(_sort->kind == Kind::Function);
// TODO Use domain and codomain as key as well // TODO Use domain and codomain as key as well
if (!m_variables.count(_name)) if (!m_variables.count(_name))
{ {
auto fSort = dynamic_pointer_cast<FunctionSort>(_sort); auto fSort = dynamic_pointer_cast<FunctionSort>(_sort);
smtAssert(fSort->codomain, ""); smtAssert(fSort->codomain);
string domain = toSmtLibSort(fSort->domain); string domain = toSmtLibSort(fSort->domain);
string codomain = toSmtLibSort(*fSort->codomain); string codomain = toSmtLibSort(*fSort->codomain);
m_variables.insert(_name); m_variables.insert(_name);

View File

@ -21,12 +21,31 @@
#include <libsolutil/Assertions.h> #include <libsolutil/Assertions.h>
#include <libsolutil/Exceptions.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 namespace solidity::smtutil
{ {
struct SMTLogicError: virtual util::Exception {}; struct SMTLogicError: virtual util::Exception {};
#define smtAssert(CONDITION, DESCRIPTION) \ /// Assertion that throws an SMTLogicError containing the given description if it is not met.
assertThrow(CONDITION, SMTLogicError, DESCRIPTION) #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" \
)
} }

View File

@ -22,6 +22,7 @@
#include <libsmtutil/Sorts.h> #include <libsmtutil/Sorts.h>
#include <libsolutil/Common.h> #include <libsolutil/Common.h>
#include <libsolutil/Numeric.h>
#include <range/v3/view.hpp> #include <range/v3/view.hpp>
@ -42,11 +43,11 @@ struct SMTSolverChoice
bool smtlib2 = false; bool smtlib2 = false;
bool z3 = false; bool z3 = false;
static constexpr SMTSolverChoice All() { return {true, true, true}; } static constexpr SMTSolverChoice All() noexcept { return {true, true, true}; }
static constexpr SMTSolverChoice CVC4() { return {true, false, false}; } static constexpr SMTSolverChoice CVC4() noexcept { return {true, false, false}; }
static constexpr SMTSolverChoice SMTLIB2() { return {false, true, false}; } static constexpr SMTSolverChoice SMTLIB2() noexcept { return {false, true, false}; }
static constexpr SMTSolverChoice Z3() { return {false, false, true}; } static constexpr SMTSolverChoice Z3() noexcept { return {false, false, true}; }
static constexpr SMTSolverChoice None() { return {false, false, false}; } static constexpr SMTSolverChoice None() noexcept { return {false, false, false}; }
static std::optional<SMTSolverChoice> fromString(std::string const& _solvers) static std::optional<SMTSolverChoice> fromString(std::string const& _solvers)
{ {
@ -65,7 +66,7 @@ struct SMTSolverChoice
return solvers; return solvers;
} }
SMTSolverChoice& operator&(SMTSolverChoice const& _other) SMTSolverChoice& operator&=(SMTSolverChoice const& _other)
{ {
cvc4 &= _other.cvc4; cvc4 &= _other.cvc4;
smtlib2 &= _other.smtlib2; smtlib2 &= _other.smtlib2;
@ -73,9 +74,10 @@ struct SMTSolverChoice
return *this; 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); } bool operator!=(SMTSolverChoice const& _other) const noexcept { return !(*this == _other); }
@ -101,9 +103,9 @@ struct SMTSolverChoice
return true; return true;
} }
bool none() { return !some(); } bool none() const noexcept { return !some(); }
bool some() { return cvc4 || smtlib2 || z3; } bool some() const noexcept { return cvc4 || smtlib2 || z3; }
bool all() { return cvc4 && smtlib2 && z3; } bool all() const noexcept { return cvc4 && smtlib2 && z3; }
}; };
enum class CheckResult enum class CheckResult

View File

@ -27,6 +27,8 @@
#include <libsolidity/ast/TypeProvider.h> #include <libsolidity/ast/TypeProvider.h>
#include <liblangutil/ErrorReporter.h> #include <liblangutil/ErrorReporter.h>
#include <limits>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;
using namespace solidity::frontend; using namespace solidity::frontend;

View File

@ -71,7 +71,7 @@ bool ContractLevelChecker::check(SourceUnit const& _sourceUnit)
findDuplicateDefinitions( findDuplicateDefinitions(
filterDeclarations<EventDefinition>(*_sourceUnit.annotation().exportedSymbols) filterDeclarations<EventDefinition>(*_sourceUnit.annotation().exportedSymbols)
); );
if (!Error::containsOnlyWarnings(m_errorReporter.errors())) if (Error::containsErrors(m_errorReporter.errors()))
noErrors = false; noErrors = false;
for (ASTPointer<ASTNode> const& node: _sourceUnit.nodes()) for (ASTPointer<ASTNode> const& node: _sourceUnit.nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
@ -97,7 +97,7 @@ bool ContractLevelChecker::check(ContractDefinition const& _contract)
checkPayableFallbackWithoutReceive(_contract); checkPayableFallbackWithoutReceive(_contract);
checkStorageSize(_contract); checkStorageSize(_contract);
return Error::containsOnlyWarnings(m_errorReporter.errors()); return !Error::containsErrors(m_errorReporter.errors());
} }
void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _contract) void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _contract)

View File

@ -36,7 +36,7 @@ bool ControlFlowAnalyzer::run()
for (auto& [pair, flow]: m_cfg.allFunctionFlows()) for (auto& [pair, flow]: m_cfg.allFunctionFlows())
analyze(*pair.function, pair.contract, *flow); 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) void ControlFlowAnalyzer::analyze(FunctionDefinition const& _function, ContractDefinition const* _contract, FunctionFlow const& _flow)

View File

@ -409,7 +409,8 @@ bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly)
void ControlFlowBuilder::visit(yul::Statement const& _statement) void ControlFlowBuilder::visit(yul::Statement const& _statement)
{ {
solAssert(m_currentNode && m_inlineAssembly, ""); 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); ASTWalker::visit(_statement);
} }
@ -496,14 +497,15 @@ void ControlFlowBuilder::operator()(yul::Identifier const& _identifier)
solAssert(m_currentNode && m_inlineAssembly, ""); solAssert(m_currentNode && m_inlineAssembly, "");
auto const& externalReferences = m_inlineAssembly->annotation().externalReferences; auto const& externalReferences = m_inlineAssembly->annotation().externalReferences;
if (externalReferences.count(&_identifier)) if (externalReferences.count(&_identifier))
{
if (auto const* declaration = dynamic_cast<VariableDeclaration const*>(externalReferences.at(&_identifier).declaration)) if (auto const* declaration = dynamic_cast<VariableDeclaration const*>(externalReferences.at(&_identifier).declaration))
{
solAssert(nativeLocationOf(_identifier) == originLocationOf(_identifier), "");
m_currentNode->variableOccurrences.emplace_back( m_currentNode->variableOccurrences.emplace_back(
*declaration, *declaration,
VariableOccurrence::Kind::Access, VariableOccurrence::Kind::Access,
_identifier.debugData->location nativeLocationOf(_identifier)
); );
} }
} }
void ControlFlowBuilder::operator()(yul::Assignment const& _assignment) void ControlFlowBuilder::operator()(yul::Assignment const& _assignment)
@ -514,11 +516,14 @@ void ControlFlowBuilder::operator()(yul::Assignment const& _assignment)
for (auto const& variable: _assignment.variableNames) for (auto const& variable: _assignment.variableNames)
if (externalReferences.count(&variable)) if (externalReferences.count(&variable))
if (auto const* declaration = dynamic_cast<VariableDeclaration const*>(externalReferences.at(&variable).declaration)) if (auto const* declaration = dynamic_cast<VariableDeclaration const*>(externalReferences.at(&variable).declaration))
{
solAssert(nativeLocationOf(variable) == originLocationOf(variable), "");
m_currentNode->variableOccurrences.emplace_back( m_currentNode->variableOccurrences.emplace_back(
*declaration, *declaration,
VariableOccurrence::Kind::Assignment, VariableOccurrence::Kind::Assignment,
variable.debugData->location nativeLocationOf(variable)
); );
}
} }
void ControlFlowBuilder::operator()(yul::FunctionCall const& _functionCall) void ControlFlowBuilder::operator()(yul::FunctionCall const& _functionCall)
@ -548,7 +553,7 @@ void ControlFlowBuilder::operator()(yul::FunctionDefinition const&)
void ControlFlowBuilder::operator()(yul::Leave const&) void ControlFlowBuilder::operator()(yul::Leave const&)
{ {
// This has to be implemented, if we ever decide to visit functions. // This has to be implemented, if we ever decide to visit functions.
solUnimplementedAssert(false, ""); solUnimplemented("");
} }
bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration) bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)

View File

@ -27,7 +27,7 @@ using namespace solidity::frontend;
bool CFG::constructFlow(ASTNode const& _astRoot) bool CFG::constructFlow(ASTNode const& _astRoot)
{ {
_astRoot.accept(*this); _astRoot.accept(*this);
return Error::containsOnlyWarnings(m_errorReporter.errors()); return !Error::containsErrors(m_errorReporter.errors());
} }

View File

@ -140,6 +140,30 @@ bool DeclarationTypeChecker::visit(StructDefinition const& _struct)
return false; 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) void DeclarationTypeChecker::endVisit(UserDefinedTypeName const& _typeName)
{ {
if (_typeName.annotation().type) if (_typeName.annotation().type)
@ -158,6 +182,8 @@ void DeclarationTypeChecker::endVisit(UserDefinedTypeName const& _typeName)
_typeName.annotation().type = TypeProvider::enumType(*enumDef); _typeName.annotation().type = TypeProvider::enumType(*enumDef);
else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration)) else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration))
_typeName.annotation().type = TypeProvider::contract(*contract); _typeName.annotation().type = TypeProvider::contract(*contract);
else if (auto userDefinedValueType = dynamic_cast<UserDefinedValueTypeDefinition const*>(declaration))
_typeName.annotation().type = TypeProvider::userDefinedValueType(*userDefinedValueType);
else else
{ {
_typeName.annotation().type = TypeProvider::emptyTuple(); _typeName.annotation().type = TypeProvider::emptyTuple();
@ -227,12 +253,13 @@ void DeclarationTypeChecker::endVisit(Mapping const& _mapping)
{ {
case Type::Category::Enum: case Type::Category::Enum:
case Type::Category::Contract: case Type::Category::Contract:
case Type::Category::UserDefinedValueType:
break; break;
default: default:
m_errorReporter.fatalTypeError( m_errorReporter.fatalTypeError(
7804_error, 7804_error,
typeName->location(), 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; break;
} }

View File

@ -60,6 +60,7 @@ private:
void endVisit(VariableDeclaration const& _variable) override; void endVisit(VariableDeclaration const& _variable) override;
bool visit(EnumDefinition const& _enum) override; bool visit(EnumDefinition const& _enum) override;
bool visit(StructDefinition const& _struct) override; bool visit(StructDefinition const& _struct) override;
void endVisit(UserDefinedValueTypeDefinition const& _userDefined) override;
bool visit(UsingForDirective const& _usingForDirective) override; bool visit(UsingForDirective const& _usingForDirective) override;
bool visit(InheritanceSpecifier const& _inheritanceSpecifier) override; bool visit(InheritanceSpecifier const& _inheritanceSpecifier) override;

View File

@ -34,6 +34,7 @@
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <regex> #include <regex>
#include <string_view>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;
@ -233,7 +234,7 @@ void DocStringTagParser::parseDocStrings(
for (auto const& [tagName, tagValue]: _annotation.docTags) for (auto const& [tagName, tagValue]: _annotation.docTags)
{ {
string static const customPrefix("custom:"); string_view static constexpr customPrefix("custom:");
if (tagName == "custom" || tagName == "custom:") if (tagName == "custom" || tagName == "custom:")
m_errorReporter.docstringParsingError( m_errorReporter.docstringParsingError(
6564_error, 6564_error,

View File

@ -102,7 +102,12 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
else else
for (Declaration const* declaration: declarations) for (Declaration const* declaration: declarations)
if (!DeclarationRegistrationHelper::registerDeclaration( 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; error = true;
} }
@ -607,13 +612,31 @@ bool DeclarationRegistrationHelper::visitNode(ASTNode& _node)
if (auto* declaration = dynamic_cast<Declaration*>(&_node)) if (auto* declaration = dynamic_cast<Declaration*>(&_node))
registerDeclaration(*declaration); 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)) if (dynamic_cast<ScopeOpener const*>(&_node))
enterNewSubScope(_node); enterNewSubScope(_node);
if (auto* variableScope = dynamic_cast<VariableScope*>(&_node)) if (auto* variableScope = dynamic_cast<VariableScope*>(&_node))
m_currentFunction = variableScope; m_currentFunction = variableScope;
if (auto* annotation = dynamic_cast<TypeDeclarationAnnotation*>(&_node.annotation()))
annotation->canonicalName = currentCanonicalName();
return true; return true;
} }
@ -663,23 +686,4 @@ void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaratio
solAssert(_declaration.annotation().contract == m_currentContract, ""); 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;
}
} }

View File

@ -176,9 +176,6 @@ private:
static bool isOverloadedFunction(Declaration const& _declaration1, Declaration const& _declaration2); 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; std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& m_scopes;
ASTNode const* m_currentScope = nullptr; ASTNode const* m_currentScope = nullptr;
VariableScope* m_currentFunction = nullptr; VariableScope* m_currentFunction = nullptr;

View File

@ -86,7 +86,8 @@ private:
int currentNode = static_cast<int>(numNodes++); int currentNode = static_cast<int>(numNodes++);
nodes[_function] = currentNode; nodes[_function] = currentNode;
nodeInv[currentNode] = _function; nodeInv[currentNode] = _function;
if (_function.overrides())
if (!_function.baseFunctions().empty())
for (auto const& baseFunction: _function.baseFunctions()) for (auto const& baseFunction: _function.baseFunctions())
addEdge(currentNode, visit(baseFunction)); addEdge(currentNode, visit(baseFunction));
else else
@ -518,7 +519,7 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
"Override changes modifier signature." "Override changes modifier signature."
); );
if (!_overriding.overrides()) if (!_overriding.overrides() && !(_super.isFunction() && _super.contract().isInterface()))
overrideError( overrideError(
_overriding, _overriding,
_super, _super,

View File

@ -36,14 +36,14 @@ using namespace solidity::frontend;
bool PostTypeChecker::check(ASTNode const& _astRoot) bool PostTypeChecker::check(ASTNode const& _astRoot)
{ {
_astRoot.accept(*this); _astRoot.accept(*this);
return Error::containsOnlyWarnings(m_errorReporter.errors()); return !Error::containsErrors(m_errorReporter.errors());
} }
bool PostTypeChecker::finalize() bool PostTypeChecker::finalize()
{ {
for (auto& checker: m_checkers) for (auto& checker: m_checkers)
checker->finalize(); checker->finalize();
return Error::containsOnlyWarnings(m_errorReporter.errors()); return !Error::containsErrors(m_errorReporter.errors());
} }
bool PostTypeChecker::visit(ContractDefinition const& _contractDefinition) bool PostTypeChecker::visit(ContractDefinition const& _contractDefinition)

View File

@ -68,5 +68,5 @@ bool PostTypeContractLevelChecker::check(ContractDefinition const& _contract)
errorHashes[hash][signature] = error->location(); errorHashes[hash][signature] = error->location();
} }
return Error::containsOnlyWarnings(m_errorReporter.errors()); return !Error::containsErrors(m_errorReporter.errors());
} }

View File

@ -201,9 +201,13 @@ bool ReferencesResolver::visit(Return const& _return)
void ReferencesResolver::operator()(yul::FunctionDefinition const& _function) 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) 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; bool wasInsideFunction = m_yulInsideFunction;
m_yulInsideFunction = true; m_yulInsideFunction = true;
@ -213,13 +217,17 @@ void ReferencesResolver::operator()(yul::FunctionDefinition const& _function)
void ReferencesResolver::operator()(yul::Identifier const& _identifier) 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; string suffix;
for (string const& s: suffixes) for (string const& s: suffixes)
if (boost::algorithm::ends_with(_identifier.name.str(), "." + s)) if (boost::algorithm::ends_with(_identifier.name.str(), "." + s))
suffix = 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()); auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str());
if (!suffix.empty()) if (!suffix.empty())
{ {
@ -238,7 +246,7 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
{ {
m_errorReporter.declarationError( m_errorReporter.declarationError(
4718_error, 4718_error,
_identifier.debugData->location, nativeLocationOf(_identifier),
"Multiple matching identifiers. Resolving overloaded identifiers is not supported." "Multiple matching identifiers. Resolving overloaded identifiers is not supported."
); );
return; return;
@ -251,7 +259,7 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
) )
m_errorReporter.declarationError( m_errorReporter.declarationError(
9467_error, 9467_error,
_identifier.debugData->location, nativeLocationOf(_identifier),
"Identifier not found. Use \".slot\" and \".offset\" to access storage variables." "Identifier not found. Use \".slot\" and \".offset\" to access storage variables."
); );
return; return;
@ -261,7 +269,7 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
{ {
m_errorReporter.declarationError( m_errorReporter.declarationError(
6578_error, 6578_error,
_identifier.debugData->location, nativeLocationOf(_identifier),
"Cannot access local Solidity variables from inside an inline assembly function." "Cannot access local Solidity variables from inside an inline assembly function."
); );
return; return;
@ -274,7 +282,10 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl) void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl)
{ {
for (auto const& identifier: _varDecl.variables) 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) if (_varDecl.value)
visit(*_varDecl.value); visit(*_varDecl.value);

View File

@ -86,7 +86,7 @@ StaticAnalyzer::~StaticAnalyzer()
bool StaticAnalyzer::analyze(SourceUnit const& _sourceUnit) bool StaticAnalyzer::analyze(SourceUnit const& _sourceUnit)
{ {
_sourceUnit.accept(*this); _sourceUnit.accept(*this);
return Error::containsOnlyWarnings(m_errorReporter.errors()); return !Error::containsErrors(m_errorReporter.errors());
} }
bool StaticAnalyzer::visit(ContractDefinition const& _contract) bool StaticAnalyzer::visit(ContractDefinition const& _contract)

View File

@ -41,7 +41,7 @@ using namespace solidity::util;
bool SyntaxChecker::checkSyntax(ASTNode const& _astRoot) bool SyntaxChecker::checkSyntax(ASTNode const& _astRoot)
{ {
_astRoot.accept(*this); _astRoot.accept(*this);
return Error::containsOnlyWarnings(m_errorReporter.errors()); return !Error::containsErrors(m_errorReporter.errors());
} }
bool SyntaxChecker::visit(SourceUnit const& _sourceUnit) bool SyntaxChecker::visit(SourceUnit const& _sourceUnit)

View File

@ -73,7 +73,7 @@ bool TypeChecker::checkTypeRequirements(SourceUnit const& _source)
m_currentSourceUnit = &_source; m_currentSourceUnit = &_source;
_source.accept(*this); _source.accept(*this);
m_currentSourceUnit = nullptr; m_currentSourceUnit = nullptr;
return Error::containsOnlyWarnings(m_errorReporter.errors()); return !Error::containsErrors(m_errorReporter.errors());
} }
Type const* TypeChecker::type(Expression const& _expression) const Type const* TypeChecker::type(Expression const& _expression) const
@ -246,7 +246,10 @@ TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(Functio
Type::Category typeCategory = typeTypePtr->actualType()->category(); Type::Category typeCategory = typeTypePtr->actualType()->category();
if (auto const* contractType = dynamic_cast<ContractType const*>(typeTypePtr->actualType())) if (auto const* contractType = dynamic_cast<ContractType const*>(typeTypePtr->actualType()))
wrongType = contractType->isSuper(); wrongType = contractType->isSuper();
else if (typeCategory != Type::Category::Integer) else if (
typeCategory != Type::Category::Integer &&
typeCategory != Type::Category::Enum
)
wrongType = true; wrongType = true;
} }
else else
@ -257,7 +260,7 @@ TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(Functio
4259_error, 4259_error,
arguments.front()->location(), arguments.front()->location(),
"Invalid type for argument in the function call. " "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." type(*arguments.front())->toString(true) + " provided."
); );
@ -314,14 +317,22 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
void TypeChecker::endVisit(ModifierDefinition const& _modifier) void TypeChecker::endVisit(ModifierDefinition const& _modifier)
{ {
if (_modifier.virtualSemantics()) if (auto const* contractDef = dynamic_cast<ContractDefinition const*>(_modifier.scope()))
if (auto const* contractDef = dynamic_cast<ContractDefinition const*>(_modifier.scope())) {
if (contractDef->isLibrary()) if (_modifier.virtualSemantics() && contractDef->isLibrary())
m_errorReporter.typeError( m_errorReporter.typeError(
3275_error, 3275_error,
_modifier.location(), _modifier.location(),
"Modifiers in a library cannot be virtual." "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()) if (!_modifier.isImplemented() && !_modifier.virtualSemantics())
m_errorReporter.typeError(8063_error, _modifier.location(), "Modifiers without implementation must be marked virtual."); 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!"); solAssert(var->type(), "Expected variable type!");
if (var->immutable()) 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; return false;
} }
if (var->isConstant()) if (var->isConstant())
@ -763,7 +774,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
{ {
m_errorReporter.typeError( m_errorReporter.typeError(
3558_error, 3558_error,
_identifier.debugData->location, nativeLocationOf(_identifier),
"Constant variable is circular." "Constant variable is circular."
); );
return false; return false;
@ -773,24 +784,24 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
if (var && !var->value()) 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; return false;
} }
else if (_context == yul::IdentifierContext::LValue) 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; 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; return false;
} }
else if (var && var->value() && !var->value()->annotation().type && !dynamic_cast<Literal const*>(var->value().get())) else if (var && var->value() && !var->value()->annotation().type && !dynamic_cast<Literal const*>(var->value().get()))
{ {
m_errorReporter.typeError( m_errorReporter.typeError(
2249_error, 2249_error,
_identifier.debugData->location, nativeLocationOf(_identifier),
"Constant variables with non-literal values cannot be forward referenced from inline assembly." "Constant variables with non-literal values cannot be forward referenced from inline assembly."
); );
return false; return false;
@ -800,7 +811,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
type(*var->value())->category() != Type::Category::RationalNumber 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; return false;
} }
} }
@ -810,24 +821,24 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
if (!identifierInfo.suffix.empty()) if (!identifierInfo.suffix.empty())
{ {
string const& suffix = identifierInfo.suffix; string const& suffix = identifierInfo.suffix;
solAssert((set<string>{"offset", "slot", "length"}).count(suffix), ""); solAssert((set<string>{"offset", "slot", "length", "selector", "address"}).count(suffix), "");
if (var->isStateVariable() || var->type()->dataStoredIn(DataLocation::Storage)) if (!var->isConstant() && (var->isStateVariable() || var->type()->dataStoredIn(DataLocation::Storage)))
{ {
if (suffix != "slot" && suffix != "offset") 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; return false;
} }
else if (_context == yul::IdentifierContext::LValue) else if (_context == yul::IdentifierContext::LValue)
{ {
if (var->isStateVariable()) 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; return false;
} }
else if (suffix != "slot") 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; return false;
} }
} }
@ -839,13 +850,26 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
{ {
if (suffix != "offset" && suffix != "length") 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; return false;
} }
} }
else 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; return false;
} }
} }
@ -853,14 +877,14 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
{ {
m_errorReporter.typeError( m_errorReporter.typeError(
1408_error, 1408_error,
_identifier.debugData->location, nativeLocationOf(_identifier),
"Only local variables are supported. To access storage variables, use the \".slot\" and \".offset\" suffixes." "Only local variables are supported. To access storage variables, use the \".slot\" and \".offset\" suffixes."
); );
return false; return false;
} }
else if (var->type()->dataStoredIn(DataLocation::Storage)) 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; return false;
} }
else if (var->type()->sizeOnStack() != 1) 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()); auto const* arrayType = dynamic_cast<ArrayType const*>(var->type());
arrayType && arrayType->isDynamicallySized() && arrayType->dataStoredIn(DataLocation::CallData) 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 else
{ {
solAssert(!var->type()->dataStoredIn(DataLocation::CallData), ""); 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; return false;
} }
} }
else if (!identifierInfo.suffix.empty()) 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; return false;
} }
else if (_context == yul::IdentifierContext::LValue) else if (_context == yul::IdentifierContext::LValue)
@ -888,7 +912,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
if (dynamic_cast<MagicVariableDeclaration const*>(declaration)) if (dynamic_cast<MagicVariableDeclaration const*>(declaration))
return false; 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; return false;
} }
@ -897,7 +921,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
solAssert(!!declaration->type(), "Type of declaration required but not yet determined."); solAssert(!!declaration->type(), "Type of declaration required but not yet determined.");
if (dynamic_cast<FunctionDefinition const*>(declaration)) 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; return false;
} }
else if (dynamic_cast<VariableDeclaration const*>(declaration)) else if (dynamic_cast<VariableDeclaration const*>(declaration))
@ -907,7 +931,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
{ {
if (!contract->isLibrary()) 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; return false;
} }
} }
@ -2470,6 +2494,13 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
returnTypes = functionType->returnParameterTypes(); returnTypes = functionType->returnParameterTypes();
break; break;
} }
case FunctionType::Kind::Wrap:
case FunctionType::Kind::Unwrap:
{
typeCheckFunctionGeneralChecks(_functionCall, functionType);
returnTypes = functionType->returnParameterTypes();
break;
}
default: default:
{ {
typeCheckFunctionCall(_functionCall, functionType); 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 // 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. // although every subexpression is, so leaving this limited for now.
if (auto tt = dynamic_cast<TypeType const*>(exprType)) 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; annotation.isPure = true;
if ( if (
auto const* functionType = dynamic_cast<FunctionType const*>(exprType); auto const* functionType = dynamic_cast<FunctionType const*>(exprType);

View File

@ -69,7 +69,7 @@ public:
if (yul::EVMDialect const* dialect = dynamic_cast<decltype(dialect)>(&m_dialect)) if (yul::EVMDialect const* dialect = dynamic_cast<decltype(dialect)>(&m_dialect))
if (yul::BuiltinFunctionForEVM const* fun = dialect->builtin(_funCall.functionName.name)) if (yul::BuiltinFunctionForEVM const* fun = dialect->builtin(_funCall.functionName.name))
if (fun->instruction) if (fun->instruction)
checkInstruction(_funCall.debugData->location, *fun->instruction); checkInstruction(nativeLocationOf(_funCall), *fun->instruction);
for (auto const& arg: _funCall.arguments) for (auto const& arg: _funCall.arguments)
std::visit(*this, arg); std::visit(*this, arg);

View File

@ -274,7 +274,7 @@ uint32_t ContractDefinition::interfaceId() const
{ {
uint32_t result{0}; uint32_t result{0};
for (auto const& function: interfaceFunctionList(false)) for (auto const& function: interfaceFunctionList(false))
result ^= util::fromBigEndian<uint32_t>(function.first.ref()); result ^= fromBigEndian<uint32_t>(function.first.ref());
return result; return result;
} }
@ -335,6 +335,17 @@ TypeNameAnnotation& TypeName::annotation() const
return initAnnotation<TypeNameAnnotation>(); 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 Type const* StructDefinition::type() const
{ {
solAssert(annotation().recursive.has_value(), "Requested struct type before DeclarationTypeChecker."); solAssert(annotation().recursive.has_value(), "Requested struct type before DeclarationTypeChecker.");

View File

@ -726,6 +726,40 @@ public:
Type const* type() const override; 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. * 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 * None of the parameters is allowed to contain mappings (not even recursively

View File

@ -211,7 +211,7 @@ struct InlineAssemblyAnnotation: StatementAnnotation
struct ExternalIdentifierInfo struct ExternalIdentifierInfo
{ {
Declaration const* declaration = nullptr; 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; std::string suffix;
size_t valueSize = size_t(-1); size_t valueSize = size_t(-1);
}; };

View File

@ -51,6 +51,7 @@ class UsingForDirective;
class StructDefinition; class StructDefinition;
class EnumDefinition; class EnumDefinition;
class EnumValue; class EnumValue;
class UserDefinedValueTypeDefinition;
class ParameterList; class ParameterList;
class FunctionDefinition; class FunctionDefinition;
class VariableDeclaration; class VariableDeclaration;

View File

@ -26,12 +26,12 @@
#include <libsolidity/ast/TypeProvider.h> #include <libsolidity/ast/TypeProvider.h>
#include <libyul/AsmJsonConverter.h> #include <libyul/AsmJsonConverter.h>
#include <libyul/AsmPrinter.h>
#include <libyul/AST.h> #include <libyul/AST.h>
#include <libyul/backends/evm/EVMDialect.h> #include <libyul/backends/evm/EVMDialect.h>
#include <libsolutil/JSON.h> #include <libsolutil/JSON.h>
#include <libsolutil/UTF8.h> #include <libsolutil/UTF8.h>
#include <libsolutil/CommonData.h>
#include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/join.hpp>
@ -172,16 +172,19 @@ void ASTJsonConverter::appendExpressionAttributes(
_attributes += exprAttributes; _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); 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["declaration"] = idOrNull(_info.second.declaration);
tuple["isSlot"] = Json::Value(_info.second.suffix == "slot"); tuple["isSlot"] = Json::Value(_info.second.suffix == "slot");
tuple["isOffset"] = Json::Value(_info.second.suffix == "offset"); tuple["isOffset"] = Json::Value(_info.second.suffix == "offset");
if (!_info.second.suffix.empty()) if (!_info.second.suffix.empty())
tuple["suffix"] = Json::Value(_info.second.suffix); tuple["suffix"] = Json::Value(_info.second.suffix);
tuple["valueSize"] = Json::Value(Json::LargestUInt(_info.second.valueSize)); tuple["valueSize"] = Json::Value(Json::LargestUInt(_info.second.valueSize));
return tuple; return tuple;
} }
@ -276,6 +279,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
make_pair("nodes", toJson(_node.subNodes())), make_pair("nodes", toJson(_node.subNodes())),
make_pair("scope", idOrNull(_node.scope())) make_pair("scope", idOrNull(_node.scope()))
}; };
addIfSet(attributes, "canonicalName", _node.annotation().canonicalName);
if (_node.annotation().unimplementedDeclarations.has_value()) if (_node.annotation().unimplementedDeclarations.has_value())
attributes.emplace_back("fullyImplemented", _node.annotation().unimplementedDeclarations->empty()); attributes.emplace_back("fullyImplemented", _node.annotation().unimplementedDeclarations->empty());
@ -354,6 +358,21 @@ bool ASTJsonConverter::visit(EnumValue const& _node)
return false; 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) bool ASTJsonConverter::visit(ParameterList const& _node)
{ {
setJsonNode(_node, "ParameterList", { setJsonNode(_node, "ParameterList", {

View File

@ -81,6 +81,7 @@ public:
bool visit(StructDefinition const& _node) override; bool visit(StructDefinition const& _node) override;
bool visit(EnumDefinition const& _node) override; bool visit(EnumDefinition const& _node) override;
bool visit(EnumValue const& _node) override; bool visit(EnumValue const& _node) override;
bool visit(UserDefinedValueTypeDefinition const& _node) override;
bool visit(ParameterList const& _node) override; bool visit(ParameterList const& _node) override;
bool visit(OverrideSpecifier const& _node) override; bool visit(OverrideSpecifier const& _node) override;
bool visit(FunctionDefinition const& _node) override; bool visit(FunctionDefinition const& _node) override;

View File

@ -61,7 +61,7 @@ map<string, ASTPointer<SourceUnit>> ASTJsonImporter::jsonToSourceUnit(map<string
m_sourceNames.emplace_back(make_shared<string const>(src.first)); m_sourceNames.emplace_back(make_shared<string const>(src.first));
for (auto const& srcPair: _sourceList) 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'."); astAssert(member(srcPair.second,"nodeType") == "SourceUnit", "The 'nodeType' of the highest node must be 'SourceUnit'.");
m_sourceUnits[srcPair.first] = createSourceUnit(srcPair.second, srcPair.first); m_sourceUnits[srcPair.first] = createSourceUnit(srcPair.second, srcPair.first);
} }
@ -133,6 +133,8 @@ ASTPointer<ASTNode> ASTJsonImporter::convertJsonToASTNode(Json::Value const& _js
return createEnumDefinition(_json); return createEnumDefinition(_json);
if (nodeType == "EnumValue") if (nodeType == "EnumValue")
return createEnumValue(_json); return createEnumValue(_json);
if (nodeType == "UserDefinedValueTypeDefinition")
return createUserDefinedValueTypeDefinition(_json);
if (nodeType == "ParameterList") if (nodeType == "ParameterList")
return createParameterList(_json); return createParameterList(_json);
if (nodeType == "OverrideSpecifier") 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) ASTPointer<ParameterList> ASTJsonImporter::createParameterList(Json::Value const& _node)
{ {
std::vector<ASTPointer<VariableDeclaration>> parameters; std::vector<ASTPointer<VariableDeclaration>> parameters;
@ -473,17 +485,17 @@ ASTPointer<VariableDeclaration> ASTJsonImporter::createVariableDeclaration(Json:
if (mutabilityStr == "constant") if (mutabilityStr == "constant")
{ {
mutability = VariableDeclaration::Mutability::Constant; mutability = VariableDeclaration::Mutability::Constant;
astAssert(memberAsBool(_node, "constant"), ""); astAssert(memberAsBool(_node, "constant"));
} }
else else
{ {
astAssert(!memberAsBool(_node, "constant"), ""); astAssert(!memberAsBool(_node, "constant"));
if (mutabilityStr == "mutable") if (mutabilityStr == "mutable")
mutability = VariableDeclaration::Mutability::Mutable; mutability = VariableDeclaration::Mutability::Mutable;
else if (mutabilityStr == "immutable") else if (mutabilityStr == "immutable")
mutability = VariableDeclaration::Mutability::Immutable; mutability = VariableDeclaration::Mutability::Immutable;
else else
astAssert(false, ""); astAssert(false);
} }
return createASTNode<VariableDeclaration>( return createASTNode<VariableDeclaration>(

View File

@ -81,6 +81,7 @@ private:
ASTPointer<ASTNode> createStructDefinition(Json::Value const& _node); ASTPointer<ASTNode> createStructDefinition(Json::Value const& _node);
ASTPointer<EnumDefinition> createEnumDefinition(Json::Value const& _node); ASTPointer<EnumDefinition> createEnumDefinition(Json::Value const& _node);
ASTPointer<EnumValue> createEnumValue(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<ParameterList> createParameterList(Json::Value const& _node);
ASTPointer<OverrideSpecifier> createOverrideSpecifier(Json::Value const& _node); ASTPointer<OverrideSpecifier> createOverrideSpecifier(Json::Value const& _node);
ASTPointer<FunctionDefinition> createFunctionDefinition(Json::Value const& _node); ASTPointer<FunctionDefinition> createFunctionDefinition(Json::Value const& _node);

View File

@ -61,6 +61,7 @@ public:
virtual bool visit(IdentifierPath& _node) { return visitNode(_node); } virtual bool visit(IdentifierPath& _node) { return visitNode(_node); }
virtual bool visit(InheritanceSpecifier& _node) { return visitNode(_node); } virtual bool visit(InheritanceSpecifier& _node) { return visitNode(_node); }
virtual bool visit(UsingForDirective& _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(StructDefinition& _node) { return visitNode(_node); }
virtual bool visit(EnumDefinition& _node) { return visitNode(_node); } virtual bool visit(EnumDefinition& _node) { return visitNode(_node); }
virtual bool visit(EnumValue& _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(IdentifierPath& _node) { endVisitNode(_node); }
virtual void endVisit(InheritanceSpecifier& _node) { endVisitNode(_node); } virtual void endVisit(InheritanceSpecifier& _node) { endVisitNode(_node); }
virtual void endVisit(UsingForDirective& _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(StructDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(EnumDefinition& _node) { endVisitNode(_node); } virtual void endVisit(EnumDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(EnumValue& _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(InheritanceSpecifier const& _node) { return visitNode(_node); }
virtual bool visit(StructDefinition 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(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(EnumDefinition const& _node) { return visitNode(_node); }
virtual bool visit(EnumValue const& _node) { return visitNode(_node); } virtual bool visit(EnumValue const& _node) { return visitNode(_node); }
virtual bool visit(ParameterList 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(IdentifierPath const& _node) { endVisitNode(_node); }
virtual void endVisit(InheritanceSpecifier const& _node) { endVisitNode(_node); } virtual void endVisit(InheritanceSpecifier const& _node) { endVisitNode(_node); }
virtual void endVisit(UsingForDirective 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(StructDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(EnumDefinition const& _node) { endVisitNode(_node); } virtual void endVisit(EnumDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(EnumValue const& _node) { endVisitNode(_node); } virtual void endVisit(EnumValue const& _node) { endVisitNode(_node); }

View File

@ -164,6 +164,26 @@ void EnumValue::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this); _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) void UsingForDirective::accept(ASTVisitor& _visitor)
{ {
if (_visitor.visit(*this)) if (_visitor.visit(*this))

View File

@ -566,9 +566,10 @@ MagicType const* TypeProvider::meta(Type const* _type)
solAssert( solAssert(
_type && ( _type && (
_type->category() == Type::Category::Contract || _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); return createAndGet<MagicType>(_type);
} }
@ -577,3 +578,8 @@ MappingType const* TypeProvider::mapping(Type const* _keyType, Type const* _valu
{ {
return createAndGet<MappingType>(_keyType, _valueType); return createAndGet<MappingType>(_keyType, _valueType);
} }
UserDefinedValueType const* TypeProvider::userDefinedValueType(UserDefinedValueTypeDefinition const& _definition)
{
return createAndGet<UserDefinedValueType>(_definition);
}

View File

@ -201,6 +201,8 @@ public:
static MappingType const* mapping(Type const* _keyType, Type const* _valueType); static MappingType const* mapping(Type const* _keyType, Type const* _valueType);
static UserDefinedValueType const* userDefinedValueType(UserDefinedValueTypeDefinition const& _definition);
private: private:
/// Global TypeProvider instance. /// Global TypeProvider instance.
static TypeProvider& instance() static TypeProvider& instance()

View File

@ -1135,7 +1135,7 @@ IntegerType const* RationalNumberType::integerType() const
return nullptr; return nullptr;
else else
return TypeProvider::integer( return TypeProvider::integer(
max(util::bytesRequired(value), 1u) * 8, max(numberEncodingSize(value), 1u) * 8,
negative ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned negative ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned
); );
} }
@ -1169,7 +1169,7 @@ FixedPointType const* RationalNumberType::fixedPointType() const
if (v > u256(-1)) if (v > u256(-1))
return nullptr; return nullptr;
unsigned totalBits = max(util::bytesRequired(v), 1u) * 8; unsigned totalBits = max(numberEncodingSize(v), 1u) * 8;
solAssert(totalBits <= 256, ""); solAssert(totalBits <= 256, "");
return TypeProvider::fixedPoint( return TypeProvider::fixedPoint(
@ -2537,6 +2537,37 @@ unsigned EnumType::memberValue(ASTString const& _member) const
solAssert(false, "Requested unknown enum value " + _member); 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 BoolResult TupleType::isImplicitlyConvertibleTo(Type const& _other) const
{ {
if (auto tupleType = dynamic_cast<TupleType const*>(&_other)) if (auto tupleType = dynamic_cast<TupleType const*>(&_other))
@ -2888,6 +2919,8 @@ string FunctionType::richIdentifier() const
case Kind::GasLeft: id += "gasleft"; break; case Kind::GasLeft: id += "gasleft"; break;
case Kind::Event: id += "event"; break; case Kind::Event: id += "event"; break;
case Kind::Error: id += "error"; 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::SetGas: id += "setgas"; break;
case Kind::SetValue: id += "setvalue"; break; case Kind::SetValue: id += "setvalue"; break;
case Kind::BlockHash: id += "blockhash"; break; case Kind::BlockHash: id += "blockhash"; break;
@ -3043,10 +3076,7 @@ u256 FunctionType::storageSize() const
bool FunctionType::leftAligned() const bool FunctionType::leftAligned() const
{ {
if (m_kind == Kind::External) return m_kind == Kind::External;
return true;
else
solAssert(false, "Alignment property of non-exportable function type requested.");
} }
unsigned FunctionType::storageBytes() const unsigned FunctionType::storageBytes() const
@ -3471,7 +3501,9 @@ bool FunctionType::isPure() const
m_kind == Kind::ABIEncodeWithSelector || m_kind == Kind::ABIEncodeWithSelector ||
m_kind == Kind::ABIEncodeWithSignature || m_kind == Kind::ABIEncodeWithSignature ||
m_kind == Kind::ABIDecode || 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) 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()) for (ASTPointer<EnumValue> const& enumValue: enumDef.members())
members.emplace_back(enumValue.get(), enumType); 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 ( else if (
auto const* arrayType = dynamic_cast<ArrayType const*>(m_actualType); auto const* arrayType = dynamic_cast<ArrayType const*>(m_actualType);
arrayType && arrayType->isByteArray() arrayType && arrayType->isByteArray()
@ -3965,9 +4025,10 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
solAssert( solAssert(
m_typeArgument && ( m_typeArgument && (
m_typeArgument->category() == Type::Category::Contract || 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) if (m_typeArgument->category() == Type::Category::Contract)
@ -3993,6 +4054,14 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
{"max", integerTypePointer}, {"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."); solAssert(false, "Unknown kind of magic.");

View File

@ -29,6 +29,7 @@
#include <liblangutil/Exceptions.h> #include <liblangutil/Exceptions.h>
#include <libsolutil/Common.h> #include <libsolutil/Common.h>
#include <libsolutil/Numeric.h>
#include <libsolutil/CommonIO.h> #include <libsolutil/CommonIO.h>
#include <libsolutil/LazyInit.h> #include <libsolutil/LazyInit.h>
#include <libsolutil/Result.h> #include <libsolutil/Result.h>
@ -174,7 +175,7 @@ public:
enum class Category enum class Category
{ {
Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, ArraySlice, 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, Mapping, TypeType, Modifier, Magic, Module,
InaccessibleDynamic InaccessibleDynamic
}; };
@ -1071,11 +1072,85 @@ public:
/// @returns the value that the string has in the Enum /// @returns the value that the string has in the Enum
unsigned int memberValue(ASTString const& _member) const; unsigned int memberValue(ASTString const& _member) const;
size_t numberOfMembers() 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: private:
EnumDefinition const& m_enum; 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. * Type that can hold a finite sequence of values of different types.
* In some cases, the components are empty pointers (when used as placeholders). * In some cases, the components are empty pointers (when used as placeholders).
@ -1144,6 +1219,8 @@ public:
RIPEMD160, ///< CALL to special contract for ripemd160 RIPEMD160, ///< CALL to special contract for ripemd160
Event, ///< syntactic sugar for LOG* Event, ///< syntactic sugar for LOG*
Error, ///< creating an error instance in revert or require 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 SetGas, ///< modify the default gas value for the function call
SetValue, ///< modify the default value transfer for the function call SetValue, ///< modify the default value transfer for the function call
BlockHash, ///< BLOCKHASH BlockHash, ///< BLOCKHASH

View File

@ -1170,6 +1170,7 @@ string ABIFunctions::abiDecodingFunctionArrayAvailableLength(ArrayType const& _t
solAssert(_type.dataStoredIn(DataLocation::Memory), ""); solAssert(_type.dataStoredIn(DataLocation::Memory), "");
if (_type.isByteArray()) if (_type.isByteArray())
return abiDecodingFunctionByteArrayAvailableLength(_type, _fromMemory); return abiDecodingFunctionByteArrayAvailableLength(_type, _fromMemory);
solAssert(_type.calldataStride() > 0, "");
string functionName = string functionName =
"abi_decode_available_length_" + "abi_decode_available_length_" +
@ -1186,11 +1187,11 @@ string ABIFunctions::abiDecodingFunctionArrayAvailableLength(ArrayType const& _t
mstore(array, length) mstore(array, length)
dst := add(array, 0x20) dst := add(array, 0x20)
</dynamic> </dynamic>
let src := offset let srcEnd := add(offset, mul(length, <stride>))
if gt(add(src, mul(length, <stride>)), end) { if gt(srcEnd, end) {
<revertInvalidStride>() <revertInvalidStride>()
} }
for { let i := 0 } lt(i, length) { i := add(i, 1) } for { let src := offset } lt(src, srcEnd) { src := add(src, <stride>) }
{ {
<?dynamicBase> <?dynamicBase>
let innerOffset := <load>(src) let innerOffset := <load>(src)
@ -1201,7 +1202,6 @@ string ABIFunctions::abiDecodingFunctionArrayAvailableLength(ArrayType const& _t
</dynamicBase> </dynamicBase>
mstore(dst, <decodingFun>(elementPos, end)) mstore(dst, <decodingFun>(elementPos, end))
dst := add(dst, 0x20) dst := add(dst, 0x20)
src := add(src, <stride>)
} }
} }
)"); )");

View File

@ -60,8 +60,3 @@ std::shared_ptr<evmasm::Assembly> Compiler::runtimeAssemblyPtr() const
solAssert(m_context.runtimeContext(), ""); solAssert(m_context.runtimeContext(), "");
return m_context.runtimeContext()->assemblyPtr(); return m_context.runtimeContext()->assemblyPtr();
} }
evmasm::AssemblyItem Compiler::functionEntryLabel(FunctionDefinition const& _function) const
{
return m_runtimeContext.functionEntryLabelIfExists(_function);
}

View File

@ -62,10 +62,6 @@ public:
std::string generatedYulUtilityCode() const { return m_context.generatedYulUtilityCode(); } std::string generatedYulUtilityCode() const { return m_context.generatedYulUtilityCode(); }
std::string runtimeGeneratedYulUtilityCode() const { return m_runtimeContext.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: private:
OptimiserSettings const m_optimiserSettings; OptimiserSettings const m_optimiserSettings;
CompilerContext m_runtimeContext; CompilerContext m_runtimeContext;

View File

@ -421,7 +421,7 @@ void CompilerContext::appendInlineAssembly(
if (stackDiff < 1 || stackDiff > 16) if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
StackTooDeepError() << StackTooDeepError() <<
errinfo_sourceLocation(_identifier.debugData->location) << errinfo_sourceLocation(nativeLocationOf(_identifier)) <<
util::errinfo_comment("Stack too deep (" + to_string(stackDiff) + "), try removing local variables.") util::errinfo_comment("Stack too deep (" + to_string(stackDiff) + "), try removing local variables.")
); );
if (_context == yul::IdentifierContext::RValue) if (_context == yul::IdentifierContext::RValue)

View File

@ -46,6 +46,7 @@
#include <stack> #include <stack>
#include <queue> #include <queue>
#include <utility> #include <utility>
#include <limits>
namespace solidity::frontend namespace solidity::frontend
{ {

View File

@ -40,7 +40,7 @@ using namespace solidity::langutil;
using solidity::util::Whiskers; using solidity::util::Whiskers;
using solidity::util::h256; using solidity::util::h256;
using solidity::util::toCompactHexWithPrefix; using solidity::toCompactHexWithPrefix;
unsigned const CompilerUtils::dataStartOffset = 4; unsigned const CompilerUtils::dataStartOffset = 4;
size_t const CompilerUtils::freeMemoryPointer = 64; size_t const CompilerUtils::freeMemoryPointer = 64;
@ -772,6 +772,33 @@ void CompilerUtils::convertType(
Type::Category stackTypeCategory = _typeOnStack.category(); Type::Category stackTypeCategory = _typeOnStack.category();
Type::Category targetTypeCategory = _targetType.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)) if (auto contrType = dynamic_cast<ContractType const*>(&_typeOnStack))
solAssert(!contrType->isSuper(), "Cannot convert magic variable \"super\""); 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) unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWords)
{ {
solAssert(_type.isValueType(), ""); 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; 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) if (funType->kind() == FunctionType::Kind::External)
isExternalFunctionType = true; isExternalFunctionType = true;
if (numBytes == 0) if (numBytes == 0)
@ -1543,21 +1573,20 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda
splitExternalFunctionType(true); splitExternalFunctionType(true);
else if (numBytes != 32) else if (numBytes != 32)
{ {
bool leftAligned = _type.category() == Type::Category::FixedBytes;
// add leading or trailing zeros by dividing/multiplying depending on alignment // add leading or trailing zeros by dividing/multiplying depending on alignment
unsigned shiftFactor = (32 - numBytes) * 8; unsigned shiftFactor = (32 - numBytes) * 8;
rightShiftNumberOnStack(shiftFactor); rightShiftNumberOnStack(shiftFactor);
if (leftAligned) if (type->leftAligned())
{ {
leftShiftNumberOnStack(shiftFactor); leftShiftNumberOnStack(shiftFactor);
cleanupNeeded = false; 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()) if (!intType->isSigned())
cleanupNeeded = false; cleanupNeeded = false;
} }
if (_fromCalldata) if (_fromCalldata)
convertType(_type, _type, cleanupNeeded, false, true); convertType(_type, *type, cleanupNeeded, false, true);
return numBytes; 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) + ")." "Memory store of more than 32 bytes requested (Type: " + _type.toString(true) + ")."
); );
bool leftAligned = _type.category() == Type::Category::FixedBytes;
if (_cleanup) if (_cleanup)
convertType(_type, _type, true); convertType(_type, _type, true);
if (numBytes != 32 && !leftAligned && !_padToWords) if (numBytes != 32 && !_type.leftAligned() && !_padToWords)
// shift the value accordingly before storing // shift the value accordingly before storing
leftShiftNumberOnStack((32 - numBytes) * 8); leftShiftNumberOnStack((32 - numBytes) * 8);

View File

@ -95,9 +95,9 @@ public:
/// @returns the number of bytes consumed in memory. /// @returns the number of bytes consumed in memory.
unsigned loadFromMemory( unsigned loadFromMemory(
unsigned _offset, unsigned _offset,
Type const& _type = *TypeProvider::uint256(), Type const& _type,
bool _fromCalldata = false, bool _fromCalldata,
bool _padToWords = false bool _padToWords
); );
/// Dynamic version of @see loadFromMemory, expects the memory offset on the stack. /// Dynamic version of @see loadFromMemory, expects the memory offset on the stack.
/// Stack pre: memory_offset /// Stack pre: memory_offset

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