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
cd build
$boost_dir=(Resolve-Path $PSScriptRoot\..\deps\boost\lib\cmake\Boost-*)
..\deps\cmake\bin\cmake -G "Visual Studio 16 2019" -DBoost_DIR="$boost_dir\" -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DCMAKE_INSTALL_PREFIX="$PSScriptRoot\..\upload" ..
..\deps\cmake\bin\cmake -G "Visual Studio 16 2019" -DBoost_DIR="$boost_dir\" -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DCMAKE_INSTALL_PREFIX="$PSScriptRoot\..\upload" -DUSE_Z3=OFF ..
if ( -not $? ) { throw "CMake configure failed." }
msbuild solidity.sln /p:Configuration=Release /m:5 /v:minimal
if ( -not $? ) { throw "Build failed." }

View File

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

View File

@ -35,6 +35,21 @@
set -eu
function validate_checksum {
local package="$1"
local expected_checksum="$2"
local actual_checksum
actual_checksum=$(sha256sum "$package")
if [[ $actual_checksum != "${expected_checksum} ${package}" ]]
then
>&2 echo "ERROR: Wrong checksum for package $package."
>&2 echo "Actual: $actual_checksum"
>&2 echo "Expected: $expected_checksum"
exit 1
fi
}
if [ ! -f /usr/local/lib/libz3.a ] # if this file does not exists (cache was not restored), rebuild dependencies
then
git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow
@ -48,23 +63,31 @@ then
./scripts/install_obsolete_jsoncpp_1_7_4.sh
# z3
z3_version="z3-4.8.12"
osx_version="osx-10.15.7"
wget "https://github.com/Z3Prover/z3/releases/download/$z3_version/$z3_version-x64-$osx_version.zip"
unzip "$z3_version-x64-$osx_version.zip"
rm -f "$z3_version-x64-$osx_version.zip"
cp "$z3_version-x64-$osx_version/bin/libz3.a" /usr/local/lib
cp "$z3_version-x64-$osx_version/bin/z3" /usr/local/bin
cp "$z3_version-x64-$osx_version"/include/* /usr/local/include
rm -rf "$z3_version-x64-$osx_version"
z3_version="4.8.12"
z3_dir="z3-${z3_version}-x64-osx-10.15.7"
z3_package="${z3_dir}.zip"
wget "https://github.com/Z3Prover/z3/releases/download/z3-${z3_version}/${z3_package}"
validate_checksum "$z3_package" a1f6ef3c99456147c4d3f2652dc6bc90951c4ab3fe7741a255eb794f0ab8938c
unzip "$z3_package"
rm "$z3_package"
cp "${z3_dir}/bin/libz3.a" /usr/local/lib
cp "${z3_dir}/bin/z3" /usr/local/bin
cp "${z3_dir}/include/"* /usr/local/include
rm -r "$z3_dir"
# evmone
wget https://github.com/ethereum/evmone/releases/download/v0.8.0/evmone-0.8.0-darwin-x86_64.tar.gz
tar xzpf evmone-0.8.0-darwin-x86_64.tar.gz -C /usr/local
rm -f evmone-0.8.0-darwin-x86_64.tar.gz
evmone_version="0.8.0"
evmone_package="evmone-${evmone_version}-darwin-x86_64.tar.gz"
wget "https://github.com/ethereum/evmone/releases/download/v${evmone_version}/${evmone_package}"
validate_checksum "$evmone_package" e8efef478822f0ed6d0493e89004181e895893f93963152a2a81589acc3a0828
tar xzpf "$evmone_package" -C /usr/local
rm "$evmone_package"
# hera
wget https://github.com/ewasm/hera/releases/download/v0.5.0/hera-0.5.0-darwin-x86_64.tar.gz
tar xzpf hera-0.5.0-darwin-x86_64.tar.gz -C /usr/local
rm -f hera-0.5.0-darwin-x86_64.tar.gz
hera_version="0.5.0"
hera_package="hera-${hera_version}-darwin-x86_64.tar.gz"
wget "https://github.com/ewasm/hera/releases/download/v${hera_version}/${hera_package}"
validate_checksum "$hera_package" 190050d7ace384ecd79ec1b1f607a9ff40e196b4eec75932958d4814d221d059
tar xzpf "$hera_package" -C /usr/local
rm "$hera_package"
fi

View File

@ -21,7 +21,7 @@ include(EthPolicy)
eth_policy()
# project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.8.8")
set(PROJECT_VERSION "0.8.10")
# OSX target needed in order to support std::visit
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
@ -34,6 +34,7 @@ endif()
option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF)
option(SOLC_STATIC_STDLIBS "Link solc against static versions of libgcc and libstdc++ on supported platforms" OFF)
option(STRICT_Z3_VERSION "Use the latest version of Z3" ON)
# Setup cccache.
include(EthCcache)
@ -63,8 +64,29 @@ configure_file("${CMAKE_SOURCE_DIR}/cmake/templates/license.h.in" include/licens
include(EthOptions)
configure_project(TESTS)
set(LATEST_Z3_VERSION "4.8.12")
set(MINIMUM_Z3_VERSION "4.8.0")
find_package(Z3)
if (${Z3_FOUND})
if (${STRICT_Z3_VERSION})
if (NOT ("${Z3_VERSION_STRING}" VERSION_EQUAL ${LATEST_Z3_VERSION}))
message(
FATAL_ERROR
"SMTChecker tests require Z3 ${LATEST_Z3_VERSION} for all tests to pass.\n\
Build with -DSTRICT_Z3_VERSION=OFF if you want to use a different version. \
You can also use -DUSE_Z3=OFF to build without Z3. In both cases use --no-smt when running tests."
)
endif()
else()
if ("${Z3_VERSION_STRING}" VERSION_LESS ${MINIMUM_Z3_VERSION})
message(
FATAL_ERROR
"Solidity requires Z3 ${MINIMUM_Z3_VERSION} or newer. You can also use -DUSE_Z3=OFF to build without Z3."
)
endif()
endif()
endif()
find_package(Z3 4.8.0)
if(${USE_Z3_DLOPEN})
add_definitions(-DHAVE_Z3)
add_definitions(-DHAVE_Z3_DLOPEN)

View File

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

View File

@ -6,23 +6,72 @@ Breaking changes:
* Inline Assembly: Consider functions, function parameters and return variables for shadowing checks.
### 0.8.8 (unreleased)
### 0.8.10 (unreleased)
Language Features:
* Inline Assembly: Support ``.address`` and ``.selector`` on external function pointers to access their address and function selector.
Compiler Features:
* Commandline Interface: Normalize paths specified on the command line and make them relative for files located inside base path.
* Immutable variables can be read at construction time once they are initialized.
* SMTChecker: Support low level ``call`` as external calls to unknown code.
* SMTChecker: Add constraints to better correlate ``address(this).balance`` and ``msg.value``.
* Commandline Interface: Disallowed the ``--experimental-via-ir`` option to be used with Standard Json, Assembler and Linker modes.
* SMTChecker: Output values for ``block.*``, ``msg.*`` and ``tx.*`` variables that are present in the called functions.
Bugfixes:
* Commandline Interface: Fix extra newline character being appended to sources passed through standard input, affecting their hashes.
* SMTChecker: Fix internal error in magic type access (``block``, ``msg``, ``tx``).
### 0.8.9 (2021-09-29)
Important Bugfixes:
* Immutables: Properly perform sign extension on signed immutables.
* User Defined Value Type: Fix storage layout of user defined value types for underlying types shorter than 32 bytes.
Bugfixes:
* AST: Export ``canonicalName`` for ``UserDefinedValueTypeDefinition`` and ``ContractDefinition``.
### 0.8.8 (2021-09-27)
Language Features:
* Inheritance: A function that overrides only a single interface function does not require the ``override`` specifier.
* Type System: Support ``type(E).min`` and ``type(E).max`` for enums.
* User Defined Value Type: allows creating a zero cost abstraction over a value type with stricter type requirements.
Compiler Features:
* Commandline Interface: Add ``--include-path`` option for specifying extra directories that may contain importable code (e.g. packaged third-party libraries).
* Commandline Interface: Do not implicitly run evm bytecode generation unless needed for the requested output.
* Commandline Interface: Normalize paths specified on the command line and make them relative for files located inside base path and/or include paths.
* Immutable variables can be read at construction time once they are initialized.
* SMTChecker: Add constraints to better correlate ``address(this).balance`` and ``msg.value``.
* SMTChecker: Support constants via modules.
* SMTChecker: Support low level ``call`` as external calls to unknown code.
* SMTChecker: Support the ``value`` option for external function calls.
* SMTChecker: Support user defined value types.
Bugfixes:
* Code Generator: Fix ICE on assigning to calldata structs and statically-sized calldata arrays in inline assembly.
* Code Generator: Use stable source order for ABI functions.
* Commandline Interface: Disallow the ``--experimental-via-ir`` option in Standard JSON, Assembler and Linker modes.
* Commandline Interface: Fix resolution of paths whitelisted with ``--allowed-paths`` or implicitly due to base path, remappings and files being compiled. Correctly handle paths that do not match imports exactly due to being relative, non-normalized or empty.
* Commandline Interface: Report optimizer options as invalid in Standard JSON and linker modes instead of ignoring them.
* Name Resolver: Fix that when importing an aliased symbol using ``import {AliasedName} from "a.sol"`` it would use the original name of the symbol and not the aliased one.
* Opcode Optimizer: Prevent the optimizer from running multiple times to avoid potential bytecode differences for referenced code.
* Parser: Properly check for multiple SPDX license identifiers next to each other and validate them.
* SMTChecker: Fix BMC's constraints regarding internal functions.
* SMTChecker: Fix false negative caused by ``push`` on storage array references returned by internal functions.
* SMTChecker: Fix false positive in external calls from constructors.
* SMTChecker: Fix internal error on some multi-source uses of ``abi.*``, cryptographic functions and constants.
* SMTChecker: Fix false negative caused by ``push`` on storage array references returned by internal functions.
* Standard JSON: Fix non-fatal errors in Yul mode being discarded if followed by a fatal error.
* Type Checker: Correct wrong error message in inline assembly complaining about ``.slot`` or ``.offset` not valid when actually ``.length`` was used.
* Type Checker: Disallow modifier declarations and definitions in interfaces.
* Yul Optimizer: Fix a crash in LoadResolver, when ``keccak256`` has particular non-identifier arguments.

View File

@ -76,7 +76,7 @@ Please follow the
if you want to help.
You can find our current feature and bug priorities for forthcoming
releases [in the projects section](https://github.com/ethereum/solidity/projects).
releases in the [projects section](https://github.com/ethereum/solidity/projects).
## Maintainers
* [@axic](https://github.com/axic)

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.
if ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux")
# TODO - Is this even necessary? Why?
# See http://stackoverflow.com/questions/19774778/when-is-it-necessary-to-use-use-the-flag-stdlib-libstdc.
add_compile_options(-stdlib=libstdc++)
# Tell Boost that we're using Clang's libc++. Not sure exactly why we need to do.
add_definitions(-DBOOST_ASIO_HAS_CLANG_LIBCXX)
# Use fancy colors in the compiler diagnostics
add_compile_options(-fcolor-diagnostics)
@ -200,6 +192,10 @@ if (SANITIZE)
elseif (sanitizer STREQUAL "undefined")
# The following flags not used by fuzzer but used by us may create problems, so consider
# disabling them: alignment, pointer-overflow.
# The following flag is not used by us to reduce terminal noise
# i.e., warnings printed on stderr: unsigned-integer-overflow
# Note: The C++ standard does not officially consider unsigned integer overflows
# to be undefined behavior since they are implementation independent.
# Flags are alphabetically sorted and are for clang v10.0
list(APPEND undefinedSanitizerChecks
alignment
@ -217,18 +213,12 @@ if (SANITIZE)
returns-nonnull-attribute
shift
signed-integer-overflow
unsigned-integer-overflow
unreachable
vla-bound
vptr
)
list(JOIN undefinedSanitizerChecks "," sanitizerChecks)
list(REMOVE_ITEM undefinedSanitizerChecks unsigned-integer-overflow)
# The fuzzer excludes reports of unsigned-integer-overflow. Hence, we remove it
# from the -fno-sanitize-recover checks. Consider reducing this list if we do not
# want to be notified about other failed checks.
list(JOIN undefinedSanitizerChecks "," dontRecoverFromChecks)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=${sanitizerChecks} -fno-sanitize-recover=${dontRecoverFromChecks}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=${sanitizerChecks} -fno-sanitize-recover=${sanitizerChecks}")
endif()
endif()

View File

@ -25,13 +25,20 @@ set(ETH_SCRIPTS_DIR ${ETH_CMAKE_DIR}/scripts)
## use multithreaded boost libraries, with -mt suffix
set(Boost_USE_MULTITHREADED ON)
option(Boost_USE_STATIC_LIBS "Link Boost statically" ON)
if(WIN32)
if (WIN32)
option(Boost_USE_STATIC_RUNTIME "Link Boost against static C++ runtime libraries" ON)
endif()
set(BOOST_COMPONENTS "filesystem;unit_test_framework;program_options;system")
find_package(Boost 1.65.0 QUIET REQUIRED COMPONENTS ${BOOST_COMPONENTS})
if (WIN32)
# Boost 1.77 fixes a bug that causes crashes on Windows for some relative paths in --allow-paths.
# See https://github.com/boostorg/filesystem/issues/201
find_package(Boost 1.77.0 QUIET REQUIRED COMPONENTS ${BOOST_COMPONENTS})
else()
# Boost 1.65 is the first to also provide boost::get for rvalue-references (#5787).
find_package(Boost 1.65.0 QUIET REQUIRED COMPONENTS ${BOOST_COMPONENTS})
endif()
# If cmake is older than boost and boost is older than 1.70,
# find_package does not define imported targets, so we have to

View File

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

View File

@ -35,9 +35,9 @@ Example
-------
The following example provides library code to access the code of another contract and
load it into a ``bytes`` variable. This is not possible with "plain Solidity" and the
idea is that reusable assembly libraries can enhance the Solidity language
without a compiler change.
load it into a ``bytes`` variable. This is possible with "plain Solidity" too, by using
``<address>.code``. But the point here is that reusable assembly libraries can enhance the
Solidity language without a compiler change.
.. code-block:: solidity
@ -124,9 +124,43 @@ Access to External Variables, Functions and Libraries
You can access Solidity variables and other identifiers by using their name.
Local variables of value type are directly usable in inline assembly.
They can both be read and assigned to.
Local variables that refer to memory or calldata evaluate to the
address of the variable in memory, resp. calldata, not the value itself.
Local variables that refer to memory evaluate to the address of the variable in memory not the value itself.
Such variables can also be assigned to, but note that an assignment will only change the pointer and not the data
and that it is your responsibility to respect Solidity's memory management.
See :ref:`Conventions in Solidity <conventions-in-solidity>`.
Similarly, local variables that refer to statically-sized calldata arrays or calldata structs
evaluate to the address of the variable in calldata, not the value itself.
The variable can also be assigned a new offset, but note that no validation to ensure that
the variable will not point beyond ``calldatasize()`` is performed.
For external function pointers the address and the function selector can be
accessed using ``x.address`` and ``x.selector``.
The selector consists of four right-aligned bytes.
Both values are can be assigned to. For example:
.. code-block:: solidity
:force:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.10 <0.9.0;
contract C {
// Assigns a new selector and address to the return variable @fun
function combineToFunctionPointer(address newAddress, uint newSelector) public pure returns (function() external fun) {
assembly {
fun.selector := newSelector
fun.address := newAddress
}
}
}
For dynamic calldata arrays, you can access
their calldata offset (in bytes) and length (number of elements) using ``x.offset`` and ``x.length``.
Both expressions can also be assigned to, but as for the static case, no validation will be performed
to ensure that the resulting data area is within the bounds of ``calldatasize()``.
For local storage variables or state variables, a single Yul identifier
is not sufficient, since they do not necessarily occupy a single full storage slot.
@ -135,9 +169,10 @@ inside that slot. To retrieve the slot pointed to by the variable ``x``, you
use ``x.slot``, and to retrieve the byte-offset you use ``x.offset``.
Using ``x`` itself will result in an error.
For dynamic calldata arrays, you can access
their calldata offset (in bytes) and length (number of elements) using ``x.offset`` and ``x.length``.
Both expressions can also be assigned to.
You can also assign to the ``.slot`` part of a local storage variable pointer.
For these (structs, arrays or mappings), the ``.offset`` part is always zero.
It is not possible to assign to the ``.slot`` or ``.offset`` part of a state variable,
though.
Local Solidity variables are available for assignments, for example:
@ -178,17 +213,6 @@ Since Solidity 0.7.0, variables and functions declared inside the
inline assembly block may not contain ``.``, but using ``.`` is
valid to access Solidity variables from outside the inline assembly block.
Assignments are possible to assembly-local variables and to function-local
variables. Take care that when you assign to variables that point to
memory or storage, you will only change the pointer and not the data.
You can assign to the ``.slot`` part of a local storage variable pointer.
For these (structs, arrays or mappings), the ``.offset`` part is always zero.
It is not possible to assign to the ``.slot`` or ``.offset`` part of a state variable,
though.
Things to Avoid
---------------
@ -199,6 +223,8 @@ functional-style opcodes, counting stack height for
variable access and removing stack slots for assembly-local variables when the end
of their block is reached.
.. _conventions-in-solidity:
Conventions in Solidity
-----------------------

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",
"name": "ABIDecodeTwoDimensionalArrayMemory",

View File

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

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
and their types have to match, potentially after an :ref:`implicit conversion <types-conversion-elementary-types>`.
.. _state-mutability:
State Mutability
================
.. index:: ! view function, function;view
.. _view-functions:
View Functions
==============
--------------
Functions can be declared ``view`` in which case they promise not to modify the state.
@ -214,7 +219,7 @@ The following statements are considered modifying the state:
.. _pure-functions:
Pure Functions
==============
--------------
Functions can be declared ``pure`` in which case they promise not to read from or modify the state.
@ -270,12 +275,17 @@ This behaviour is also in line with the ``STATICCALL`` opcode.
not do state-changing operations, but it cannot check that the contract that will be called
at runtime is actually of that type.
.. _special-functions:
Special Functions
=================
.. index:: ! receive ether function, function;receive ! receive
.. _receive-ether-function:
Receive Ether Function
======================
----------------------
A contract can have at most one ``receive`` function, declared using
``receive() external payable { ... }``
@ -346,7 +356,7 @@ Below you can see an example of a Sink contract that uses function ``receive``.
.. _fallback-function:
Fallback Function
=================
-----------------
A contract can have at most one ``fallback`` function, declared using either ``fallback () external [payable]``
or ``fallback (bytes calldata _input) external [payable] returns (bytes memory _output)``

View File

@ -303,6 +303,13 @@ contracts can no longer change the behaviour of that function.
outside of interfaces. In interfaces, all functions are
automatically considered ``virtual``.
.. note::
Starting from Solidity 0.8.8, the ``override`` keyword is not
required when overriding an interface function, except for the
case where the function is defined in multiple bases.
Public state variables can override external functions if the
parameter and return types of the function matches the getter function
of the variable:

View File

@ -6,12 +6,14 @@
Interfaces
**********
Interfaces are similar to abstract contracts, but they cannot have any functions implemented. There are further restrictions:
Interfaces are similar to abstract contracts, but they cannot have any functions implemented.
There are further restrictions:
- They cannot inherit from other contracts, but they can inherit from other interfaces.
- All declared functions must be external.
- They cannot declare a constructor.
- They cannot declare state variables.
- They cannot declare modifiers.
Some of these restrictions might be lifted in the future.
@ -33,10 +35,10 @@ Interfaces are denoted by their own keyword:
Contracts can inherit interfaces as they would inherit other contracts.
All functions declared in interfaces are implicitly ``virtual``, which means that
they can be overridden. This does not automatically mean that an overriding function
can be overridden again - this is only possible if the overriding
function is marked ``virtual``.
All functions declared in interfaces are implicitly ``virtual`` and any
functions that override them do not need the ``override`` keyword.
This does not automatically mean that an overriding function can be overridden again -
this is only possible if the overriding function is marked ``virtual``.
Interfaces can inherit from other interfaces. This has the same rules as normal
inheritance.

View File

@ -100,12 +100,13 @@ Running the Tests
-----------------
Solidity includes different types of tests, most of them bundled into the
`Boost C++ Test Framework <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/index.html>`_ application ``soltest``.
`Boost C++ Test Framework <https://www.boost.org/doc/libs/release/libs/test/doc/html/index.html>`_ application ``soltest``.
Running ``build/test/soltest`` or its wrapper ``scripts/soltest.sh`` is sufficient for most changes.
The ``./scripts/tests.sh`` script executes most Solidity tests automatically,
including those bundled into the `Boost C++ Test Framework <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/index.html>`_ application ``soltest`` (or its wrapper ``scripts/soltest.sh``),
as well as command line tests and compilation tests.
including those bundled into the `Boost C++ Test Framework <https://www.boost.org/doc/libs/release/libs/test/doc/html/index.html>`_
application ``soltest`` (or its wrapper ``scripts/soltest.sh``), as well as command line tests and
compilation tests.
The test system automatically tries to discover the location of the ``evmone`` library
starting from the current directory. The required file is called ``libevmone.so`` on Linux systems,
@ -137,9 +138,9 @@ Or, for example, to run all the tests for the yul disambiguator:
See especially:
- `show_progress (-p) <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/show_progress.html>`_ to show test completion,
- `run_test (-t) <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/run_test.html>`_ to run specific tests cases, and
- `report-level (-r) <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/report_level.html>`_ give a more detailed report.
- `show_progress (-p) <https://www.boost.org/doc/libs/release/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/show_progress.html>`_ to show test completion,
- `run_test (-t) <https://www.boost.org/doc/libs/release/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/run_test.html>`_ to run specific tests cases, and
- `report-level (-r) <https://www.boost.org/doc/libs/release/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/report_level.html>`_ give a more detailed report.
.. note ::

View File

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

View File

@ -19,6 +19,7 @@ sourceUnit: (
| constantVariableDeclaration
| structDefinition
| enumDefinition
| userDefinedValueTypeDefinition
| errorDefinition
)* EOF;
@ -89,6 +90,7 @@ contractBodyElement:
| receiveFunctionDefinition
| structDefinition
| enumDefinition
| userDefinedValueTypeDefinition
| stateVariableDeclaration
| eventDefinition
| errorDefinition
@ -247,6 +249,11 @@ structMember: type=typeName name=identifier Semicolon;
* Definition of an enum. Can occur at top-level within a source unit or within a contract, library or interface.
*/
enumDefinition: Enum name=identifier LBrace enumValues+=identifier (Comma enumValues+=identifier)* RBrace;
/**
* Definition of a user defined value type. Can occur at top-level within a source unit or within a contract, library or interface.
*/
userDefinedValueTypeDefinition:
Type name=identifier Is elementaryTypeName[true] Semicolon;
/**
* The declaration of a state variable.

View File

@ -313,7 +313,8 @@ The following are dependencies for all builds of Solidity:
+===================================+=======================================================+
| `CMake`_ (version 3.13+) | Cross-platform build file generator. |
+-----------------------------------+-------------------------------------------------------+
| `Boost`_ (version 1.65+) | C++ libraries. |
| `Boost`_ (version 1.77+ on | C++ libraries. |
| Windows, 1.65+ otherwise) | |
+-----------------------------------+-------------------------------------------------------+
| `Git`_ | Command-line tool for retrieving source code. |
+-----------------------------------+-------------------------------------------------------+
@ -335,6 +336,16 @@ The following are dependencies for all builds of Solidity:
Starting from 0.5.10 linking against Boost 1.70+ should work without manual intervention.
.. note::
The default build configuration requires a specific Z3 version (the latest one at the time the
code was last updated). Changes introduced between Z3 releases often result in slightly different
(but still valid) results being returned. Our SMT tests do not account for these differences and
will likely fail with a different version than the one they were written for. This does not mean
that a build using a different version is faulty. If you pass ``-DSTRICT_Z3_VERSION=OFF`` option
to CMake, you can build with any version that satisfies the requirement given in the table above.
If you do this, however, please remember to pass the ``--no-smt`` option to ``scripts/tests.sh``
to skip the SMT tests.
Minimum Compiler Versions
^^^^^^^^^^^^^^^^^^^^^^^^^
@ -378,6 +389,8 @@ You need to install the following dependencies for Windows builds of Solidity:
+-----------------------------------+-------------------------------------------------------+
| `Visual Studio 2019`_ (Optional) | C++ compiler and dev environment. |
+-----------------------------------+-------------------------------------------------------+
| `Boost`_ (version 1.77+) | C++ libraries. |
+-----------------------------------+-------------------------------------------------------+
If you already have one IDE and only need the compiler and libraries,
you could install Visual Studio 2019 Build Tools.

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
The source unit name of a file loaded this way is constructed by converting its path to a
canonical form and making it relative to the base path if it is located inside.
See :ref:`Base Path Normalization and Stripping <base-path-normalization-and-stripping>` for
canonical form and, if possible, making it relative to either the base path or one of the
include paths.
See :ref:`CLI Path Normalization and Stripping <cli-path-normalization-and-stripping>` for
a detailed description of this process.
.. index:: standard JSON
@ -295,16 +296,36 @@ Here are some examples of what you can expect if they are not:
The use of relative imports containing leading ``..`` segments is not recommended.
The same effect can be achieved in a more reliable way by using direct imports with
:ref:`base path <base-path>` and :ref:`import remapping <import-remapping>`.
:ref:`base path and include paths <base-and-include-paths>`.
.. index:: ! base path, --base-path
.. _base-path:
.. index:: ! base path, ! --base-path, ! include paths, ! --include-path
.. _base-and-include-paths:
Base Path
=========
Base Path and Include Paths
===========================
The base path specifies the directory that the Host Filesystem Loader will load files from.
It is simply prepended to a source unit name before the filesystem lookup is performed.
The base path and include paths represent directories that the Host Filesystem Loader will load files from.
When a source unit name is passed to the loader, it prepends the base path to it and performs a
filesystem lookup.
If the lookup does not succeed, the same is done with all directories on the include path list.
It is recommended to set the base path to the root directory of your project and use include paths to
specify additional locations that may contain libraries your project depends on.
This lets you import from these libraries in a uniform way, no matter where they are located in the
filesystem relative to your project.
For example, if you use npm to install packages and your contract imports
``@openzeppelin/contracts/utils/Strings.sol``, you can use these options to tell the compiler that
the library can be found in one of the npm package directories:
.. code-block:: bash
solc contract.sol \
--base-path . \
--include-path node_modules/ \
--include-path /usr/local/lib/node_modules/
Your contract will compile (with the same exact metadata) no matter whether you install the library
in the local or global package directory or even directly under your project root.
By default the base path is empty, which leaves the source unit name unchanged.
When the source unit name is a relative path, this results in the file being looked up in the
@ -314,10 +335,23 @@ interpreted as absolute paths on disk.
If the base path itself is relative, it is interpreted as relative to the current working directory
of the compiler.
.. _base-path-normalization-and-stripping:
.. note::
Base Path Normalization and Stripping
-------------------------------------
Include paths cannot have empty values and must be used together with a non-empty base path.
.. note::
Include paths and base path can overlap as long as it does not make import resolution ambiguous.
For example, you can specify a directory inside base path as an include directory or have an
include directory that is a subdirectory of another include directory.
The compiler will only issue an error if the source unit name passed to the Host Filesystem
Loader represents an existing path when combined with multiple include paths or an include path
and base path.
.. _cli-path-normalization-and-stripping:
CLI Path Normalization and Stripping
------------------------------------
On the command line the compiler behaves just as you would expect from any other program:
it accepts paths in a format native to the platform and relative paths are relative to the current
@ -326,7 +360,7 @@ The source unit names assigned to files whose paths are specified on the command
should not change just because the project is being compiled on a different platform or because the
compiler happens to have been invoked from a different directory.
To achieve this, paths to source files coming from the command line must be converted to a canonical
form, and, if possible, made relative to the base path.
form, and, if possible, made relative to the base path or one of the include paths.
The normalization rules are as follows:
@ -357,7 +391,8 @@ The normalization rules are as follows:
You can avoid such situations by ensuring that all the files are available within a single
directory tree on the same drive.
Once canonicalized, the base path is stripped from all source file paths that start with it.
After normalization the compiler attempts to make the source file path relative.
It tries the base path first and then the include paths in the order they were given.
If the base path is empty or not specified, it is treated as if it was equal to the path to the
current working directory (with all symbolic links resolved).
The result is accepted only if the normalized directory path is the exact prefix of the normalized
@ -366,6 +401,16 @@ Otherwise the file path remains absolute.
This makes the conversion unambiguous and ensures that the relative path does not start with ``../``.
The resulting file path becomes the source unit name.
.. note::
The relative path produced by stripping must remain unique within the base path and include paths.
For example the compiler will issue an error for the following command if both
``/project/contract.sol`` and ``/lib/contract.sol`` exist:
.. code-block:: bash
solc /project/contract.sol --base-path /project --include-path /lib
.. note::
Prior to version 0.8.8, CLI path stripping was not performed and the only normalization applied
@ -373,6 +418,74 @@ The resulting file path becomes the source unit name.
When working with older versions of the compiler it is recommended to invoke the compiler from
the base path and to only use relative paths on the command line.
.. index:: ! allowed paths, ! --allow-paths, remapping; target
.. _allowed-paths:
Allowed Paths
=============
As a security measure, the Host Filesystem Loader will refuse to load files from outside of a few
locations that are considered safe by default:
- Outside of Standard JSON mode:
- The directories containing input files listed on the command line.
- The directories used as :ref:`remapping <import-remapping>` targets.
If the target is not a directory (i.e does not end with ``/``, ``/.`` or ``/..``) the directory
containing the target is used instead.
- Base path and include paths.
- In Standard JSON mode:
- Base path and include paths.
Additional directories can be whitelisted using the ``--allow-paths`` option.
The option accepts a comma-separated list of paths:
.. code-block:: bash
cd /home/user/project/
solc token/contract.sol \
lib/util.sol=libs/util.sol \
--base-path=token/ \
--include-path=/lib/ \
--allow-paths=../utils/,/tmp/libraries
When the compiler is invoked with the command shown above, the Host Filesystem Loader will allow
importing files from the following directories:
- ``/home/user/project/token/`` (because ``token/`` contains the input file and also because it is
the base path),
- ``/lib/`` (because ``/lib/`` is one of the include paths),
- ``/home/user/project/libs/`` (because ``libs/`` is a directory containing a remapping target),
- ``/home/user/utils/`` (because of ``../utils/`` passed to ``--allow-paths``),
- ``/tmp/libraries/`` (because of ``/tmp/libraries`` passed to ``--allow-paths``),
.. note::
The working directory of the compiler is one of the paths allowed by default only if it
happens to be the base path (or the base path is not specified or has an empty value).
.. note::
The compiler does not check if allowed paths actually exist and whether they are directories.
Non-existent or empty paths are simply ignored.
If an allowed path matches a file rather than a directory, the file is considered whitelisted, too.
.. note::
Allowed paths are case-sensitive even if the filesystem is not.
The case must exactly match the one used in your imports.
For example ``--allow-paths tokens`` will not match ``import "Tokens/IERC20.sol"``.
.. warning::
Files and directories only reachable through symbolic links from allowed directories are not
automatically whitelisted.
For example if ``token/contract.sol`` in the example above was actually a symlink pointing at
``/etc/passwd`` the compiler would refuse to load it unless ``/etc/`` was one of the allowed
paths too.
.. index:: ! remapping; import, ! import; remapping, ! remapping; context, ! remapping; prefix, ! remapping; target
.. _import-remapping:
@ -426,6 +539,13 @@ Loader, which will then look in ``/project/dapp-bin/library/iterable_mapping.sol
would need to recreate parts of your local directory structure in the VFS and (if you rely on
Host Filesystem Loader) also in the host filesystem.
To avoid having your local directory structure embedded in the metadata, it is recommended to
designate the directories containing libraries as *include paths* instead.
For example, in the example above ``--include-path /home/user/packages/`` would let you use
imports starting with ``mymath/``.
Unlike remapping, the option on its own will not make ``mymath`` appear as ``@math`` but this
can be achieved by creating a symbolic link or renaming the package subdirectory.
As a more complex example, suppose you rely on a module that uses an old version of dapp-bin that
you checked out to ``/project/dapp-bin_old``, then you can run:

View File

@ -353,6 +353,10 @@ code.
Always use the latest version of the compiler to be notified about all recently
introduced warnings.
Messages of type ``info`` issued by the compiler are not dangerous, and simply
represent extra suggestions and optional information that the compiler thinks
might be useful to the user.
Restrict the Amount of Ether
============================

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:
``a += e`` is equivalent to ``a = a + e``. The operators ``-=``, ``*=``, ``/=``, ``%=``,
``|=``, ``&=`` and ``^=`` are defined accordingly. ``a++`` and ``a--`` are equivalent
``|=``, ``&=``, ``^=``, ``<<=`` and ``>>=`` are defined accordingly. ``a++`` and ``a--`` are equivalent
to ``a += 1`` / ``a -= 1`` but the expression itself still has the previous value
of ``a``. In contrast, ``--a`` and ``++a`` have the same effect on ``a`` but
return the value after the change.

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 right operand must be of unsigned type, trying to shift by an signed type will produce a compilation error.
The right operand must be of unsigned type, trying to shift by a signed type will produce a compilation error.
Shifts can be "simulated" using multiplication by powers of two in the following way. Note that the truncation
to the type of the left operand is always performed at the end, but not mentioned explicitly.
@ -587,11 +587,14 @@ Enums cannot have more than 256 members.
The data representation is the same as for enums in C: The options are represented by
subsequent unsigned integer values starting from ``0``.
Using ``type(NameOfEnum).min`` and ``type(NameOfEnum).max`` you can get the
smallest and respectively largest value of the given enum.
.. code-block:: solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
pragma solidity ^0.8.8;
contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
@ -612,11 +615,83 @@ subsequent unsigned integer values starting from ``0``.
function getDefaultChoice() public pure returns (uint) {
return uint(defaultChoice);
}
function getLargestValue() public pure returns (ActionChoices) {
return type(ActionChoices).max;
}
function getSmallestValue() public pure returns (ActionChoices) {
return type(ActionChoices).min;
}
}
.. note::
Enums can also be declared on the file level, outside of contract or library definitions.
.. index:: ! user defined value type, custom type
.. _user-defined-value-types:
User Defined Value Types
------------------------
A user defined value type allows creating a zero cost abstraction over an elementary value type.
This is similar to an alias, but with stricter type requirements.
A user defined value type is defined using ``type C is V``, where ``C`` is the name of the newly
introduced type and ``V`` has to be a built-in value type (the "underlying type"). The function
``C.wrap`` is used to convert from the underlying type to the custom type. Similarly, the
function ``C.unwrap`` is used to convert from the custom type to the underlying type.
The type ``C`` does not have any operators or bound member functions. In particular, even the
operator ``==`` is not defined. Explicit and implicit conversions to and from other types are
disallowed.
The data-representation of values of such types are inherited from the underlying type
and the underlying type is also used in the ABI.
The following example illustrates a custom type ``UFixed256x18`` representing a decimal fixed point
type with 18 decimals and a minimal library to do arithmetic operations on the type.
.. code-block:: solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
// Represent a 18 decimal, 256 bit wide fixed point type using a user defined value type.
type UFixed256x18 is uint256;
/// A minimal library to do fixed point operations on UFixed256x18.
library FixedMath {
uint constant multiplier = 10**18;
/// Adds two UFixed256x18 numbers. Reverts on overflow, relying on checked
/// arithmetic on uint256.
function add(UFixed256x18 a, UFixed256x18 b) internal pure returns (UFixed256x18) {
return UFixed256x18.wrap(UFixed256x18.unwrap(a) + UFixed256x18.unwrap(b));
}
/// Multiplies UFixed256x18 and uint256. Reverts on overflow, relying on checked
/// arithmetic on uint256.
function mul(UFixed256x18 a, uint256 b) internal pure returns (UFixed256x18) {
return UFixed256x18.wrap(UFixed256x18.unwrap(a) * b);
}
/// Take the floor of a UFixed256x18 number.
/// @return the largest integer that does not exceed `a`.
function floor(UFixed256x18 a) internal pure returns (uint256) {
return UFixed256x18.unwrap(a) / multiplier;
}
/// Turns a uint256 into a UFixed256x18 of the same value.
/// Reverts if the integer is too large.
function toUFixed256x18(uint256 a) internal pure returns (UFixed256x18) {
return UFixed256x18.wrap(a * multiplier);
}
}
Notice how ``UFixed256x18.wrap`` and ``FixedMath.toUFixed256x18`` have the same signature but
perform two very different operations: The ``UFixed256x18.wrap`` function returns a ``UFixed256x18``
that has the same data representation as the input, whereas ``toUFixed256x18`` returns a
``UFixed256x18`` that has the same numerical value.
.. index:: ! function type, ! type; function

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 way constants like large numbers or strings are stored
.. index:: allowed paths, --allow-paths, base path, --base-path
.. index:: allowed paths, --allow-paths, base path, --base-path, include paths, --include-path
Base Path and Import Remapping
------------------------------
@ -47,16 +47,13 @@ it is also possible to provide :ref:`path redirects <import-remapping>` using ``
This essentially instructs the compiler to search for anything starting with
``github.com/ethereum/dapp-bin/`` under ``/usr/local/lib/dapp-bin``.
``solc`` will not read files from the filesystem that lie outside of
the remapping targets and outside of the directories where explicitly specified source
files reside, so things like ``import "/etc/passwd";`` only work if you add ``/=/`` as a remapping.
When accessing the filesystem to search for imports, :ref:`paths that do not start with ./
or ../ <relative-imports>` are treated as relative to the directory specified using
``--base-path`` option (or the current working directory if base path is not specified).
Furthermore, the part added via ``--base-path`` will not appear in the contract metadata.
or ../ <direct-imports>` are treated as relative to the directories specified using
``--base-path`` and ``--include-path`` options (or the current working directory if base path is not specified).
Furthermore, the part of the path added via these options will not appear in the contract metadata.
For security reasons the compiler has restrictions on what directories it can access.
For security reasons the compiler has :ref:`restrictions on what directories it can access <allowed-paths>`.
Directories of source files specified on the command line and target paths of
remappings are automatically allowed to be accessed by the file reader, but everything
else is rejected by default.
@ -437,7 +434,7 @@ Output Description
.. code-block:: javascript
{
// Optional: not present if no errors/warnings were encountered
// Optional: not present if no errors/warnings/infos were encountered
"errors": [
{
// Optional: Location within the source file.
@ -460,7 +457,7 @@ Output Description
"type": "TypeError",
// Mandatory: Component where the error originated, such as "general", "ewasm", etc.
"component": "general",
// Mandatory ("error" or "warning")
// Mandatory ("error", "warning" or "info", but please note that this may be extended in the future)
"severity": "error",
// Optional: unique code for the cause of the error
"errorCode": "3141",
@ -604,6 +601,7 @@ Error Types
11. ``CompilerError``: Invalid use of the compiler stack - this should be reported as an issue.
12. ``FatalError``: Fatal error not processed correctly - this should be reported as an issue.
13. ``Warning``: A warning, which didn't stop the compilation, but should be addressed if possible.
14. ``Info``: Information that the compiler thinks the user might find useful, but is not dangerous and does not necessarily need to be addressed.
.. _compiler-tools:

View File

@ -174,9 +174,32 @@ whitespace, i.e. there is no terminating ``;`` or newline required.
Literals
--------
As literals, you can use integer constants in decimal or hexadecimal notation
or strings as ASCII (`"abc"`) or HEX strings (`hex"616263"`) of up to
32 bytes length.
As literals, you can use:
- Integer constants in decimal or hexadecimal notation.
- ASCII strings (e.g. ``"abc"``), which may contain hex escapes ``\xNN`` and Unicode escapes ``\uNNNN`` where ``N`` are hexadecimal digits.
- Hex strings (e.g. ``hex"616263"``).
In the EVM dialect of Yul, literals represent 256-bit words as follows:
- Decimal or hexadecimal constants must be less than ``2**256``.
They represent the 256-bit word with that value as an unsigned integer in big endian encoding.
- An ASCII string is first viewed as a byte sequence, by viewing
a non-escape ASCII character as a single byte whose value is the ASCII code,
an escape ``\xNN`` as single byte with that value, and
an escape ``\uNNNN`` as the UTF-8 sequence of bytes for that code point.
The byte sequence must not exceed 32 bytes.
The byte sequence is padded with zeros on the right to reach 32 bytes in length;
in other words, the string is stored left-aligned.
The padded byte sequence represents a 256-bit word whose most significant 8 bits are the ones from the first byte,
i.e. the bytes are interpreted in big endian form.
- A hex string is first viewed as a byte sequence, by viewing
each pair of contiguous hex digits as a byte.
The byte sequence must not exceed 32 bytes (i.e. 64 hex digits), and is treated as above.
When compiling for the EVM, this will be translated into an
appropriate ``PUSHi`` instruction. In the following example,
@ -184,8 +207,7 @@ appropriate ``PUSHi`` instruction. In the following example,
bitwise ``and`` with the string "abc" is computed.
The final value is assigned to a local variable called ``x``.
Strings are stored left-aligned and cannot be longer than 32 bytes.
The limit does not apply to string literals passed to builtin functions that require
The 32-byte limit above does not apply to string literals passed to builtin functions that require
literal arguments (e.g. ``setimmutable`` or ``loadimmutable``). Those strings never end up in the
generated bytecode.

View File

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

View File

@ -165,9 +165,9 @@ protected:
/// Does the same operations as @a optimise, but should only be applied to a sub and
/// returns the replaced tags. Also takes an argument containing the tags of this assembly
/// that are referenced in a super-assembly.
std::map<u256, u256> optimiseInternal(OptimiserSettings const& _settings, std::set<size_t> _tagsReferencedFromOutside);
std::map<u256, u256> const& optimiseInternal(OptimiserSettings const& _settings, std::set<size_t> _tagsReferencedFromOutside);
unsigned bytesRequired(unsigned subTagSize) const;
unsigned codeSize(unsigned subTagSize) const;
private:
static Json::Value createJsonValue(
@ -210,6 +210,10 @@ protected:
/// This map is used only for sub-assemblies which are not direct sub-assemblies (where path is having more than one value).
std::map<std::vector<size_t>, size_t> m_subPaths;
/// Contains the tag replacements relevant for super-assemblies.
/// If set, it means the optimizer has run and we will not run it again.
std::optional<std::map<u256, u256>> m_tagReplacements;
mutable LinkerObject m_assembledObject;
mutable std::vector<size_t> m_tagPositionsInBytecode;

View File

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

View File

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

View File

@ -25,6 +25,8 @@
#pragma once
#include <libsolutil/Common.h>
#include <libsolutil/Numeric.h>
#include <cstddef>
#include <vector>
@ -64,9 +66,14 @@ private:
/// Iterator that skips tags and skips to the end if (all branches of) the control
/// flow does not continue to the next instruction.
/// If the arguments are supplied to the constructor, replaces items on the fly.
struct BlockIterator: std::iterator<std::forward_iterator_tag, AssemblyItem const>
struct BlockIterator
{
public:
using iterator_category = std::forward_iterator_tag;
using value_type = AssemblyItem const;
using difference_type = std::ptrdiff_t;
using pointer = AssemblyItem const*;
using reference = AssemblyItem const&;
BlockIterator(
AssemblyItems::const_iterator _it,
AssemblyItems::const_iterator _end,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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.
// https://www.boost.org/doc/libs/1_68_0/libs/multiprecision/doc/html/boost_multiprecision/map/hist.html#boost_multiprecision.map.hist.multiprecision_2_3_1_boost_1_64
// https://www.boost.org/doc/libs/release/libs/multiprecision/doc/html/boost_multiprecision/map/hist.html#boost_multiprecision.map.hist.multiprecision_2_3_1_boost_1_64
template <class S> S shlWorkaround(S const& _x, unsigned _amount)
{
return u256((bigint(_x) << _amount) & u256(-1));

View File

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

View File

@ -128,3 +128,19 @@ string_view CharStream::text(SourceLocation const& _location) const
static_cast<size_t>(_location.end - _location.start)
);
}
string CharStream::singleLineSnippet(string const& _sourceCode, SourceLocation const& _location)
{
if (!_location.hasText())
return {};
if (static_cast<size_t>(_location.start) >= _sourceCode.size())
return {};
string cut = _sourceCode.substr(static_cast<size_t>(_location.start), static_cast<size_t>(_location.end - _location.start));
auto newLinePos = cut.find_first_of("\n\r");
if (newLinePos != string::npos)
cut = cut.substr(0, newLinePos) + "...";
return cut;
}

View File

@ -118,6 +118,15 @@ public:
/// Returns an empty string view if the source location does not `hasText()`.
std::string_view text(SourceLocation const& _location) const;
/// @returns the first line of the referenced source fragment. If the fragment is longer than
/// one line, appends an ellipsis to indicate that.
std::string singleLineSnippet(SourceLocation const& _location) const
{
return singleLineSnippet(m_source, _location);
}
static std::string singleLineSnippet(std::string const& _sourceCode, SourceLocation const& _location);
private:
std::string m_source;
std::string m_name;

View File

@ -94,6 +94,16 @@ bool ErrorReporter::checkForExcessiveErrors(Error::Type _type)
if (m_warningCount >= c_maxWarningsAllowed)
return true;
}
else if (_type == Error::Type::Info)
{
m_infoCount++;
if (m_infoCount == c_maxInfosAllowed)
m_errorList.push_back(make_shared<Error>(2833_error, Error::Type::Info, "There are more than 256 infos. Ignoring the rest."));
if (m_infoCount >= c_maxInfosAllowed)
return true;
}
else
{
m_errorCount++;
@ -242,3 +252,12 @@ void ErrorReporter::docstringParsingError(ErrorId _error, SourceLocation const&
_description
);
}
void ErrorReporter::info(
ErrorId _error,
SourceLocation const& _location,
string const& _description
)
{
error(_error, Error::Type::Info, _location, _description);
}

View File

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

View File

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

View File

@ -28,6 +28,10 @@
#include <libsolutil/CommonData.h>
#include <liblangutil/SourceLocation.h>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/facilities/empty.hpp>
#include <boost/preprocessor/facilities/overload.hpp>
#include <string>
#include <utility>
#include <vector>
@ -45,18 +49,68 @@ struct FatalError: virtual util::Exception {};
struct UnimplementedFeatureError: virtual util::Exception {};
struct InvalidAstError: virtual util::Exception {};
/// Assertion that throws an InternalCompilerError containing the given description if it is not met.
#define solAssert(CONDITION, DESCRIPTION) \
assertThrow(CONDITION, ::solidity::langutil::InternalCompilerError, DESCRIPTION)
#if !BOOST_PP_VARIADICS_MSVC
#define solAssert(...) BOOST_PP_OVERLOAD(solAssert_,__VA_ARGS__)(__VA_ARGS__)
#else
#define solAssert(...) BOOST_PP_CAT(BOOST_PP_OVERLOAD(solAssert_,__VA_ARGS__)(__VA_ARGS__),BOOST_PP_EMPTY())
#endif
#define solUnimplementedAssert(CONDITION, DESCRIPTION) \
assertThrow(CONDITION, ::solidity::langutil::UnimplementedFeatureError, DESCRIPTION)
#define solAssert_1(CONDITION) \
solAssert_2(CONDITION, "")
#define solAssert_2(CONDITION, DESCRIPTION) \
assertThrowWithDefaultDescription( \
CONDITION, \
::solidity::langutil::InternalCompilerError, \
DESCRIPTION, \
"Solidity assertion failed" \
)
/// Assertion that throws an UnimplementedFeatureError containing the given description if it is not met.
#if !BOOST_PP_VARIADICS_MSVC
#define solUnimplementedAssert(...) BOOST_PP_OVERLOAD(solUnimplementedAssert_,__VA_ARGS__)(__VA_ARGS__)
#else
#define solUnimplementedAssert(...) BOOST_PP_CAT(BOOST_PP_OVERLOAD(solUnimplementedAssert_,__VA_ARGS__)(__VA_ARGS__),BOOST_PP_EMPTY())
#endif
#define solUnimplementedAssert_1(CONDITION) \
solUnimplementedAssert_2(CONDITION, "")
#define solUnimplementedAssert_2(CONDITION, DESCRIPTION) \
assertThrowWithDefaultDescription( \
CONDITION, \
::solidity::langutil::UnimplementedFeatureError, \
DESCRIPTION, \
"Unimplemented feature" \
)
/// Helper that unconditionally reports an unimplemented feature.
#define solUnimplemented(DESCRIPTION) \
solUnimplementedAssert(false, DESCRIPTION)
#define astAssert(CONDITION, DESCRIPTION) \
assertThrow(CONDITION, ::solidity::langutil::InvalidAstError, DESCRIPTION)
/// Assertion that throws an InvalidAstError containing the given description if it is not met.
#if !BOOST_PP_VARIADICS_MSVC
#define astAssert(...) BOOST_PP_OVERLOAD(astAssert_,__VA_ARGS__)(__VA_ARGS__)
#else
#define astAssert(...) BOOST_PP_CAT(BOOST_PP_OVERLOAD(astAssert_,__VA_ARGS__)(__VA_ARGS__),BOOST_PP_EMPTY())
#endif
#define astAssert_1(CONDITION) \
astAssert_2(CONDITION, "")
#define astAssert_2(CONDITION, DESCRIPTION) \
assertThrowWithDefaultDescription( \
CONDITION, \
::solidity::langutil::InvalidAstError, \
DESCRIPTION, \
"AST assertion failed" \
)
using errorSourceLocationInfo = std::pair<std::string, SourceLocation>;
@ -120,7 +174,15 @@ public:
ParserError,
TypeError,
SyntaxError,
Warning
Warning,
Info
};
enum class Severity
{
Error,
Warning,
Info
};
Error(
@ -139,21 +201,63 @@ public:
static Error const* containsErrorOfType(ErrorList const& _list, Error::Type _type)
{
for (auto e: _list)
{
if (e->type() == _type)
return e.get();
}
return nullptr;
}
static bool containsOnlyWarnings(ErrorList const& _list)
static Severity errorSeverity(Type _type)
{
if (_type == Type::Info)
return Severity::Info;
if (_type == Type::Warning)
return Severity::Warning;
return Severity::Error;
}
static bool isError(Severity _severity)
{
return _severity == Severity::Error;
}
static bool isError(Type _type)
{
return isError(errorSeverity(_type));
}
static bool containsErrors(ErrorList const& _list)
{
for (auto e: _list)
{
if (e->type() != Type::Warning)
if (isError(e->type()))
return true;
return false;
}
return true;
static std::string formatErrorSeverity(Severity _severity)
{
if (_severity == Severity::Info)
return "Info";
if (_severity == Severity::Warning)
return "Warning";
solAssert(isError(_severity), "");
return "Error";
}
static std::string formatErrorSeverityLowercase(Severity _severity)
{
switch (_severity)
{
case Severity::Info:
return "info";
case Severity::Warning:
return "warning";
case Severity::Error:
solAssert(isError(_severity), "");
return "error";
}
solAssert(false, "");
}
private:
ErrorId m_errorId;
Type m_type;

View File

@ -104,7 +104,7 @@ protected:
void fatalParserError(ErrorId _error, SourceLocation const& _location, std::string const& _description);
std::shared_ptr<Scanner> m_scanner;
/// The reference to the list of errors and warning to add errors/warnings during parsing
/// The reference to the list of errors, warnings and infos to add errors/warnings/infos during parsing
ErrorReporter& m_errorReporter;
/// Current recursion depth during parsing.
size_t m_recursionDepth = 0;

View File

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

View File

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

View File

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

View File

@ -41,7 +41,7 @@ struct LineColumn
struct SourceReference
{
std::string message; ///< A message that relates to this source reference (such as a warning or an error message).
std::string message; ///< A message that relates to this source reference (such as a warning, info or an error message).
std::string sourceName; ///< Underlying source name (for example the filename).
LineColumn position; ///< Actual (error) position this source reference is surrounding.
bool multiline = {false}; ///< Indicates whether the actual SourceReference is truncated to one line.
@ -64,12 +64,12 @@ namespace SourceReferenceExtractor
struct Message
{
SourceReference primary;
std::string category; // "Error", "Warning", ...
std::string severity; // "Error", "Warning", "Info", ...
std::vector<SourceReference> secondary;
std::optional<ErrorId> errorId;
};
Message extract(CharStreamProvider const& _charStreamProvider, util::Exception const& _exception, std::string _category);
Message extract(CharStreamProvider const& _charStreamProvider, util::Exception const& _exception, std::string _severity);
Message extract(CharStreamProvider const& _charStreamProvider, Error const& _error);
SourceReference extract(CharStreamProvider const& _charStreamProvider, SourceLocation const* _location, std::string message = "");
}

View File

@ -164,7 +164,7 @@ void SourceReferenceFormatter::printSourceLocation(SourceReference const& _ref)
void SourceReferenceFormatter::printExceptionInformation(SourceReferenceExtractor::Message const& _msg)
{
// exception header line
errorColored() << _msg.category;
errorColored() << _msg.severity;
if (m_withErrorIds && _msg.errorId.has_value())
errorColored() << " (" << _msg.errorId.value().error << ")";
messageColored() << ": " << _msg.primary.message << '\n';
@ -181,9 +181,9 @@ void SourceReferenceFormatter::printExceptionInformation(SourceReferenceExtracto
m_stream << '\n';
}
void SourceReferenceFormatter::printExceptionInformation(util::Exception const& _exception, std::string const& _category)
void SourceReferenceFormatter::printExceptionInformation(util::Exception const& _exception, std::string const& _severity)
{
printExceptionInformation(SourceReferenceExtractor::extract(m_charStreamProvider, _exception, _category));
printExceptionInformation(SourceReferenceExtractor::extract(m_charStreamProvider, _exception, _severity));
}
void SourceReferenceFormatter::printErrorInformation(ErrorList const& _errors)

View File

@ -52,7 +52,7 @@ public:
/// Prints source location if it is given.
void printSourceLocation(SourceReference const& _ref);
void printExceptionInformation(SourceReferenceExtractor::Message const& _msg);
void printExceptionInformation(util::Exception const& _exception, std::string const& _category);
void printExceptionInformation(util::Exception const& _exception, std::string const& _severity);
void printErrorInformation(langutil::ErrorList const& _errors);
void printErrorInformation(Error const& _error);
@ -77,7 +77,7 @@ public:
{
return formatExceptionInformation(
_error,
(_error.type() == Error::Type::Warning) ? "Warning" : "Error",
Error::formatErrorSeverity(Error::errorSeverity(_error.type())),
_charStreamProvider
);
}

View File

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

View File

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

View File

@ -21,12 +21,31 @@
#include <libsolutil/Assertions.h>
#include <libsolutil/Exceptions.h>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/facilities/empty.hpp>
#include <boost/preprocessor/facilities/overload.hpp>
namespace solidity::smtutil
{
struct SMTLogicError: virtual util::Exception {};
#define smtAssert(CONDITION, DESCRIPTION) \
assertThrow(CONDITION, SMTLogicError, DESCRIPTION)
/// Assertion that throws an SMTLogicError containing the given description if it is not met.
#if !BOOST_PP_VARIADICS_MSVC
#define smtAssert(...) BOOST_PP_OVERLOAD(smtAssert_,__VA_ARGS__)(__VA_ARGS__)
#else
#define smtAssert(...) BOOST_PP_CAT(BOOST_PP_OVERLOAD(smtAssert_,__VA_ARGS__)(__VA_ARGS__),BOOST_PP_EMPTY())
#endif
#define smtAssert_1(CONDITION) \
smtAssert_2(CONDITION, "")
#define smtAssert_2(CONDITION, DESCRIPTION) \
assertThrowWithDefaultDescription( \
CONDITION, \
::solidity::smtutil::SMTLogicError, \
DESCRIPTION, \
"SMT assertion failed" \
)
}

View File

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

View File

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

View File

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

View File

@ -36,7 +36,7 @@ bool ControlFlowAnalyzer::run()
for (auto& [pair, flow]: m_cfg.allFunctionFlows())
analyze(*pair.function, pair.contract, *flow);
return Error::containsOnlyWarnings(m_errorReporter.errors());
return !Error::containsErrors(m_errorReporter.errors());
}
void ControlFlowAnalyzer::analyze(FunctionDefinition const& _function, ContractDefinition const* _contract, FunctionFlow const& _flow)

View File

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

View File

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

View File

@ -140,6 +140,30 @@ bool DeclarationTypeChecker::visit(StructDefinition const& _struct)
return false;
}
void DeclarationTypeChecker::endVisit(UserDefinedValueTypeDefinition const& _userDefined)
{
TypeName const* typeName = _userDefined.underlyingType();
solAssert(typeName, "");
if (!dynamic_cast<ElementaryTypeName const*>(typeName))
m_errorReporter.fatalTypeError(
8657_error,
typeName->location(),
"The underlying type for a user defined value type has to be an elementary value type."
);
Type const* type = typeName->annotation().type;
solAssert(type, "");
solAssert(!dynamic_cast<UserDefinedValueType const*>(type), "");
if (!type->isValueType())
m_errorReporter.typeError(
8129_error,
_userDefined.location(),
"The underlying type of the user defined value type \"" +
_userDefined.name() +
"\" is not a value type."
);
}
void DeclarationTypeChecker::endVisit(UserDefinedTypeName const& _typeName)
{
if (_typeName.annotation().type)
@ -158,6 +182,8 @@ void DeclarationTypeChecker::endVisit(UserDefinedTypeName const& _typeName)
_typeName.annotation().type = TypeProvider::enumType(*enumDef);
else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration))
_typeName.annotation().type = TypeProvider::contract(*contract);
else if (auto userDefinedValueType = dynamic_cast<UserDefinedValueTypeDefinition const*>(declaration))
_typeName.annotation().type = TypeProvider::userDefinedValueType(*userDefinedValueType);
else
{
_typeName.annotation().type = TypeProvider::emptyTuple();
@ -227,12 +253,13 @@ void DeclarationTypeChecker::endVisit(Mapping const& _mapping)
{
case Type::Category::Enum:
case Type::Category::Contract:
case Type::Category::UserDefinedValueType:
break;
default:
m_errorReporter.fatalTypeError(
7804_error,
typeName->location(),
"Only elementary types, contract types or enums are allowed as mapping keys."
"Only elementary types, user defined value types, contract types or enums are allowed as mapping keys."
);
break;
}

View File

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

View File

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

View File

@ -102,7 +102,12 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
else
for (Declaration const* declaration: declarations)
if (!DeclarationRegistrationHelper::registerDeclaration(
target, *declaration, alias.alias.get(), &alias.location, false, m_errorReporter
target,
*declaration,
alias.alias ? alias.alias.get() : &alias.symbol->name(),
&alias.location,
false,
m_errorReporter
))
error = true;
}
@ -607,13 +612,31 @@ bool DeclarationRegistrationHelper::visitNode(ASTNode& _node)
if (auto* declaration = dynamic_cast<Declaration*>(&_node))
registerDeclaration(*declaration);
if (auto* annotation = dynamic_cast<TypeDeclarationAnnotation*>(&_node.annotation()))
{
string canonicalName = dynamic_cast<Declaration const&>(_node).name();
solAssert(!canonicalName.empty(), "");
for (
ASTNode const* scope = m_currentScope;
scope != nullptr;
scope = m_scopes[scope]->enclosingNode()
)
if (auto decl = dynamic_cast<Declaration const*>(scope))
{
solAssert(!decl->name().empty(), "");
canonicalName = decl->name() + "." + canonicalName;
}
annotation->canonicalName = canonicalName;
}
if (dynamic_cast<ScopeOpener const*>(&_node))
enterNewSubScope(_node);
if (auto* variableScope = dynamic_cast<VariableScope*>(&_node))
m_currentFunction = variableScope;
if (auto* annotation = dynamic_cast<TypeDeclarationAnnotation*>(&_node.annotation()))
annotation->canonicalName = currentCanonicalName();
return true;
}
@ -663,23 +686,4 @@ void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaratio
solAssert(_declaration.annotation().contract == m_currentContract, "");
}
string DeclarationRegistrationHelper::currentCanonicalName() const
{
string ret;
for (
ASTNode const* scope = m_currentScope;
scope != nullptr;
scope = m_scopes[scope]->enclosingNode()
)
{
if (auto decl = dynamic_cast<Declaration const*>(scope))
{
if (!ret.empty())
ret = "." + ret;
ret = decl->name() + ret;
}
}
return ret;
}
}

View File

@ -176,9 +176,6 @@ private:
static bool isOverloadedFunction(Declaration const& _declaration1, Declaration const& _declaration2);
/// @returns the canonical name of the current scope.
std::string currentCanonicalName() const;
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& m_scopes;
ASTNode const* m_currentScope = nullptr;
VariableScope* m_currentFunction = nullptr;

View File

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

View File

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

View File

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

View File

@ -201,9 +201,13 @@ bool ReferencesResolver::visit(Return const& _return)
void ReferencesResolver::operator()(yul::FunctionDefinition const& _function)
{
validateYulIdentifierName(_function.name, _function.debugData->location);
solAssert(nativeLocationOf(_function) == originLocationOf(_function), "");
validateYulIdentifierName(_function.name, nativeLocationOf(_function));
for (yul::TypedName const& varName: _function.parameters + _function.returnVariables)
validateYulIdentifierName(varName.name, varName.debugData->location);
{
solAssert(nativeLocationOf(varName) == originLocationOf(varName), "");
validateYulIdentifierName(varName.name, nativeLocationOf(varName));
}
bool wasInsideFunction = m_yulInsideFunction;
m_yulInsideFunction = true;
@ -213,13 +217,17 @@ void ReferencesResolver::operator()(yul::FunctionDefinition const& _function)
void ReferencesResolver::operator()(yul::Identifier const& _identifier)
{
static set<string> suffixes{"slot", "offset", "length"};
solAssert(nativeLocationOf(_identifier) == originLocationOf(_identifier), "");
static set<string> suffixes{"slot", "offset", "length", "address", "selector"};
string suffix;
for (string const& s: suffixes)
if (boost::algorithm::ends_with(_identifier.name.str(), "." + s))
suffix = s;
// Could also use `pathFromCurrentScope`, split by '.'
// Could also use `pathFromCurrentScope`, split by '.'.
// If we do that, suffix should only be set for when it has a special
// meaning, not for normal identifierPaths.
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str());
if (!suffix.empty())
{
@ -238,7 +246,7 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
{
m_errorReporter.declarationError(
4718_error,
_identifier.debugData->location,
nativeLocationOf(_identifier),
"Multiple matching identifiers. Resolving overloaded identifiers is not supported."
);
return;
@ -251,7 +259,7 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
)
m_errorReporter.declarationError(
9467_error,
_identifier.debugData->location,
nativeLocationOf(_identifier),
"Identifier not found. Use \".slot\" and \".offset\" to access storage variables."
);
return;
@ -261,7 +269,7 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
{
m_errorReporter.declarationError(
6578_error,
_identifier.debugData->location,
nativeLocationOf(_identifier),
"Cannot access local Solidity variables from inside an inline assembly function."
);
return;
@ -274,7 +282,10 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl)
{
for (auto const& identifier: _varDecl.variables)
validateYulIdentifierName(identifier.name, identifier.debugData->location);
{
solAssert(nativeLocationOf(identifier) == originLocationOf(identifier), "");
validateYulIdentifierName(identifier.name, nativeLocationOf(identifier));
}
if (_varDecl.value)
visit(*_varDecl.value);

View File

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

View File

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

View File

@ -73,7 +73,7 @@ bool TypeChecker::checkTypeRequirements(SourceUnit const& _source)
m_currentSourceUnit = &_source;
_source.accept(*this);
m_currentSourceUnit = nullptr;
return Error::containsOnlyWarnings(m_errorReporter.errors());
return !Error::containsErrors(m_errorReporter.errors());
}
Type const* TypeChecker::type(Expression const& _expression) const
@ -246,7 +246,10 @@ TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(Functio
Type::Category typeCategory = typeTypePtr->actualType()->category();
if (auto const* contractType = dynamic_cast<ContractType const*>(typeTypePtr->actualType()))
wrongType = contractType->isSuper();
else if (typeCategory != Type::Category::Integer)
else if (
typeCategory != Type::Category::Integer &&
typeCategory != Type::Category::Enum
)
wrongType = true;
}
else
@ -257,7 +260,7 @@ TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(Functio
4259_error,
arguments.front()->location(),
"Invalid type for argument in the function call. "
"A contract type or an integer type is required, but " +
"An enum type, contract type or an integer type is required, but " +
type(*arguments.front())->toString(true) + " provided."
);
@ -314,15 +317,23 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
void TypeChecker::endVisit(ModifierDefinition const& _modifier)
{
if (_modifier.virtualSemantics())
if (auto const* contractDef = dynamic_cast<ContractDefinition const*>(_modifier.scope()))
if (contractDef->isLibrary())
{
if (_modifier.virtualSemantics() && contractDef->isLibrary())
m_errorReporter.typeError(
3275_error,
_modifier.location(),
"Modifiers in a library cannot be virtual."
);
if (contractDef->isInterface())
m_errorReporter.typeError(
6408_error,
_modifier.location(),
"Modifiers cannot be defined or declared in interfaces."
);
}
if (!_modifier.isImplemented() && !_modifier.virtualSemantics())
m_errorReporter.typeError(8063_error, _modifier.location(), "Modifiers without implementation must be marked virtual.");
}
@ -754,7 +765,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
solAssert(var->type(), "Expected variable type!");
if (var->immutable())
{
m_errorReporter.typeError(3773_error, _identifier.debugData->location, "Assembly access to immutable variables is not supported.");
m_errorReporter.typeError(3773_error, nativeLocationOf(_identifier), "Assembly access to immutable variables is not supported.");
return false;
}
if (var->isConstant())
@ -763,7 +774,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
{
m_errorReporter.typeError(
3558_error,
_identifier.debugData->location,
nativeLocationOf(_identifier),
"Constant variable is circular."
);
return false;
@ -773,24 +784,24 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
if (var && !var->value())
{
m_errorReporter.typeError(3224_error, _identifier.debugData->location, "Constant has no value.");
m_errorReporter.typeError(3224_error, nativeLocationOf(_identifier), "Constant has no value.");
return false;
}
else if (_context == yul::IdentifierContext::LValue)
{
m_errorReporter.typeError(6252_error, _identifier.debugData->location, "Constant variables cannot be assigned to.");
m_errorReporter.typeError(6252_error, nativeLocationOf(_identifier), "Constant variables cannot be assigned to.");
return false;
}
else if (!identifierInfo.suffix.empty())
else if (identifierInfo.suffix == "slot" || identifierInfo.suffix == "offset")
{
m_errorReporter.typeError(6617_error, _identifier.debugData->location, "The suffixes .offset and .slot can only be used on non-constant storage variables.");
m_errorReporter.typeError(6617_error, nativeLocationOf(_identifier), "The suffixes .offset and .slot can only be used on non-constant storage variables.");
return false;
}
else if (var && var->value() && !var->value()->annotation().type && !dynamic_cast<Literal const*>(var->value().get()))
{
m_errorReporter.typeError(
2249_error,
_identifier.debugData->location,
nativeLocationOf(_identifier),
"Constant variables with non-literal values cannot be forward referenced from inline assembly."
);
return false;
@ -800,7 +811,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
type(*var->value())->category() != Type::Category::RationalNumber
))
{
m_errorReporter.typeError(7615_error, _identifier.debugData->location, "Only direct number constants and references to such constants are supported by inline assembly.");
m_errorReporter.typeError(7615_error, nativeLocationOf(_identifier), "Only direct number constants and references to such constants are supported by inline assembly.");
return false;
}
}
@ -810,24 +821,24 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
if (!identifierInfo.suffix.empty())
{
string const& suffix = identifierInfo.suffix;
solAssert((set<string>{"offset", "slot", "length"}).count(suffix), "");
if (var->isStateVariable() || var->type()->dataStoredIn(DataLocation::Storage))
solAssert((set<string>{"offset", "slot", "length", "selector", "address"}).count(suffix), "");
if (!var->isConstant() && (var->isStateVariable() || var->type()->dataStoredIn(DataLocation::Storage)))
{
if (suffix != "slot" && suffix != "offset")
{
m_errorReporter.typeError(4656_error, _identifier.debugData->location, "State variables only support \".slot\" and \".offset\".");
m_errorReporter.typeError(4656_error, nativeLocationOf(_identifier), "State variables only support \".slot\" and \".offset\".");
return false;
}
else if (_context == yul::IdentifierContext::LValue)
{
if (var->isStateVariable())
{
m_errorReporter.typeError(4713_error, _identifier.debugData->location, "State variables cannot be assigned to - you have to use \"sstore()\".");
m_errorReporter.typeError(4713_error, nativeLocationOf(_identifier), "State variables cannot be assigned to - you have to use \"sstore()\".");
return false;
}
else if (suffix != "slot")
{
m_errorReporter.typeError(9739_error, _identifier.debugData->location, "Only .slot can be assigned to.");
m_errorReporter.typeError(9739_error, nativeLocationOf(_identifier), "Only .slot can be assigned to.");
return false;
}
}
@ -839,13 +850,26 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
{
if (suffix != "offset" && suffix != "length")
{
m_errorReporter.typeError(1536_error, _identifier.debugData->location, "Calldata variables only support \".offset\" and \".length\".");
m_errorReporter.typeError(1536_error, nativeLocationOf(_identifier), "Calldata variables only support \".offset\" and \".length\".");
return false;
}
}
else if (auto const* fpType = dynamic_cast<FunctionTypePointer>(var->type()))
{
if (suffix != "selector" && suffix != "address")
{
m_errorReporter.typeError(9272_error, nativeLocationOf(_identifier), "Variables of type function pointer only support \".selector\" and \".address\".");
return false;
}
if (fpType->kind() != FunctionType::Kind::External)
{
m_errorReporter.typeError(8533_error, nativeLocationOf(_identifier), "Only Variables of type external function pointer support \".selector\" and \".address\".");
return false;
}
}
else
{
m_errorReporter.typeError(3622_error, _identifier.debugData->location, "The suffix \"." + suffix + "\" is not supported by this variable or type.");
m_errorReporter.typeError(3622_error, nativeLocationOf(_identifier), "The suffix \"." + suffix + "\" is not supported by this variable or type.");
return false;
}
}
@ -853,14 +877,14 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
{
m_errorReporter.typeError(
1408_error,
_identifier.debugData->location,
nativeLocationOf(_identifier),
"Only local variables are supported. To access storage variables, use the \".slot\" and \".offset\" suffixes."
);
return false;
}
else if (var->type()->dataStoredIn(DataLocation::Storage))
{
m_errorReporter.typeError(9068_error, _identifier.debugData->location, "You have to use the \".slot\" or \".offset\" suffix to access storage reference variables.");
m_errorReporter.typeError(9068_error, nativeLocationOf(_identifier), "You have to use the \".slot\" or \".offset\" suffix to access storage reference variables.");
return false;
}
else if (var->type()->sizeOnStack() != 1)
@ -869,18 +893,18 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
auto const* arrayType = dynamic_cast<ArrayType const*>(var->type());
arrayType && arrayType->isDynamicallySized() && arrayType->dataStoredIn(DataLocation::CallData)
)
m_errorReporter.typeError(1397_error, _identifier.debugData->location, "Call data elements cannot be accessed directly. Use \".offset\" and \".length\" to access the calldata offset and length of this array and then use \"calldatacopy\".");
m_errorReporter.typeError(1397_error, nativeLocationOf(_identifier), "Call data elements cannot be accessed directly. Use \".offset\" and \".length\" to access the calldata offset and length of this array and then use \"calldatacopy\".");
else
{
solAssert(!var->type()->dataStoredIn(DataLocation::CallData), "");
m_errorReporter.typeError(9857_error, _identifier.debugData->location, "Only types that use one stack slot are supported.");
m_errorReporter.typeError(9857_error, nativeLocationOf(_identifier), "Only types that use one stack slot are supported.");
}
return false;
}
}
else if (!identifierInfo.suffix.empty())
{
m_errorReporter.typeError(7944_error, _identifier.debugData->location, "The suffixes \".offset\", \".slot\" and \".length\" can only be used with variables.");
m_errorReporter.typeError(7944_error, nativeLocationOf(_identifier), "The suffixes \".offset\", \".slot\" and \".length\" can only be used with variables.");
return false;
}
else if (_context == yul::IdentifierContext::LValue)
@ -888,7 +912,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
if (dynamic_cast<MagicVariableDeclaration const*>(declaration))
return false;
m_errorReporter.typeError(1990_error, _identifier.debugData->location, "Only local variables can be assigned to in inline assembly.");
m_errorReporter.typeError(1990_error, nativeLocationOf(_identifier), "Only local variables can be assigned to in inline assembly.");
return false;
}
@ -897,7 +921,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
solAssert(!!declaration->type(), "Type of declaration required but not yet determined.");
if (dynamic_cast<FunctionDefinition const*>(declaration))
{
m_errorReporter.declarationError(2025_error, _identifier.debugData->location, "Access to functions is not allowed in inline assembly.");
m_errorReporter.declarationError(2025_error, nativeLocationOf(_identifier), "Access to functions is not allowed in inline assembly.");
return false;
}
else if (dynamic_cast<VariableDeclaration const*>(declaration))
@ -907,7 +931,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
{
if (!contract->isLibrary())
{
m_errorReporter.typeError(4977_error, _identifier.debugData->location, "Expected a library.");
m_errorReporter.typeError(4977_error, nativeLocationOf(_identifier), "Expected a library.");
return false;
}
}
@ -2470,6 +2494,13 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
returnTypes = functionType->returnParameterTypes();
break;
}
case FunctionType::Kind::Wrap:
case FunctionType::Kind::Unwrap:
{
typeCheckFunctionGeneralChecks(_functionCall, functionType);
returnTypes = functionType->returnParameterTypes();
break;
}
default:
{
typeCheckFunctionCall(_functionCall, functionType);
@ -2907,7 +2938,10 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
// TODO some members might be pure, but for example `address(0x123).balance` is not pure
// although every subexpression is, so leaving this limited for now.
if (auto tt = dynamic_cast<TypeType const*>(exprType))
if (tt->actualType()->category() == Type::Category::Enum)
if (
tt->actualType()->category() == Type::Category::Enum ||
tt->actualType()->category() == Type::Category::UserDefinedValueType
)
annotation.isPure = true;
if (
auto const* functionType = dynamic_cast<FunctionType const*>(exprType);

View File

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

View File

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

View File

@ -726,6 +726,40 @@ public:
Type const* type() const override;
};
/**
* User defined value types, i.e., custom types, for example, `type MyInt is int`. Allows creating a
* zero cost abstraction over value type with stricter type requirements.
*/
class UserDefinedValueTypeDefinition: public Declaration
{
public:
UserDefinedValueTypeDefinition(
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> _name,
SourceLocation _nameLocation,
ASTPointer<TypeName> _underlyingType
):
Declaration(_id, _location, _name, std::move(_nameLocation), Visibility::Default),
m_underlyingType(std::move(_underlyingType))
{
}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
Type const* type() const override;
TypeDeclarationAnnotation& annotation() const override;
TypeName const* underlyingType() const { return m_underlyingType.get(); }
bool isVisibleViaContractTypeAccess() const override { return true; }
private:
/// The name of the underlying type
ASTPointer<TypeName> m_underlyingType;
};
/**
* Parameter list, used as function parameter list, return list and for try and catch.
* None of the parameters is allowed to contain mappings (not even recursively

View File

@ -211,7 +211,7 @@ struct InlineAssemblyAnnotation: StatementAnnotation
struct ExternalIdentifierInfo
{
Declaration const* declaration = nullptr;
/// Suffix used, one of "slot", "offset", "length" or empty.
/// Suffix used, one of "slot", "offset", "length", "address", "selector" or empty.
std::string suffix;
size_t valueSize = size_t(-1);
};

View File

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

View File

@ -26,12 +26,12 @@
#include <libsolidity/ast/TypeProvider.h>
#include <libyul/AsmJsonConverter.h>
#include <libyul/AsmPrinter.h>
#include <libyul/AST.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libsolutil/JSON.h>
#include <libsolutil/UTF8.h>
#include <libsolutil/CommonData.h>
#include <boost/algorithm/string/join.hpp>
@ -172,16 +172,19 @@ void ASTJsonConverter::appendExpressionAttributes(
_attributes += exprAttributes;
}
Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair<yul::Identifier const* ,InlineAssemblyAnnotation::ExternalIdentifierInfo> _info) const
Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair<yul::Identifier const*, InlineAssemblyAnnotation::ExternalIdentifierInfo> _info) const
{
Json::Value tuple(Json::objectValue);
tuple["src"] = sourceLocationToString(_info.first->debugData->location);
tuple["src"] = sourceLocationToString(nativeLocationOf(*_info.first));
tuple["declaration"] = idOrNull(_info.second.declaration);
tuple["isSlot"] = Json::Value(_info.second.suffix == "slot");
tuple["isOffset"] = Json::Value(_info.second.suffix == "offset");
if (!_info.second.suffix.empty())
tuple["suffix"] = Json::Value(_info.second.suffix);
tuple["valueSize"] = Json::Value(Json::LargestUInt(_info.second.valueSize));
return tuple;
}
@ -276,6 +279,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
make_pair("nodes", toJson(_node.subNodes())),
make_pair("scope", idOrNull(_node.scope()))
};
addIfSet(attributes, "canonicalName", _node.annotation().canonicalName);
if (_node.annotation().unimplementedDeclarations.has_value())
attributes.emplace_back("fullyImplemented", _node.annotation().unimplementedDeclarations->empty());
@ -354,6 +358,21 @@ bool ASTJsonConverter::visit(EnumValue const& _node)
return false;
}
bool ASTJsonConverter::visit(UserDefinedValueTypeDefinition const& _node)
{
solAssert(_node.underlyingType(), "");
std::vector<pair<string, Json::Value>> attributes = {
make_pair("name", _node.name()),
make_pair("nameLocation", sourceLocationToString(_node.nameLocation())),
make_pair("underlyingType", toJson(*_node.underlyingType()))
};
addIfSet(attributes, "canonicalName", _node.annotation().canonicalName);
setJsonNode(_node, "UserDefinedValueTypeDefinition", std::move(attributes));
return false;
}
bool ASTJsonConverter::visit(ParameterList const& _node)
{
setJsonNode(_node, "ParameterList", {

View File

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

View File

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

View File

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

View File

@ -61,6 +61,7 @@ public:
virtual bool visit(IdentifierPath& _node) { return visitNode(_node); }
virtual bool visit(InheritanceSpecifier& _node) { return visitNode(_node); }
virtual bool visit(UsingForDirective& _node) { return visitNode(_node); }
virtual bool visit(UserDefinedValueTypeDefinition& _node) { return visitNode(_node); }
virtual bool visit(StructDefinition& _node) { return visitNode(_node); }
virtual bool visit(EnumDefinition& _node) { return visitNode(_node); }
virtual bool visit(EnumValue& _node) { return visitNode(_node); }
@ -116,6 +117,7 @@ public:
virtual void endVisit(IdentifierPath& _node) { endVisitNode(_node); }
virtual void endVisit(InheritanceSpecifier& _node) { endVisitNode(_node); }
virtual void endVisit(UsingForDirective& _node) { endVisitNode(_node); }
virtual void endVisit(UserDefinedValueTypeDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(StructDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(EnumDefinition& _node) { endVisitNode(_node); }
virtual void endVisit(EnumValue& _node) { endVisitNode(_node); }
@ -194,6 +196,7 @@ public:
virtual bool visit(InheritanceSpecifier const& _node) { return visitNode(_node); }
virtual bool visit(StructDefinition const& _node) { return visitNode(_node); }
virtual bool visit(UsingForDirective const& _node) { return visitNode(_node); }
virtual bool visit(UserDefinedValueTypeDefinition const& _node) { return visitNode(_node); }
virtual bool visit(EnumDefinition const& _node) { return visitNode(_node); }
virtual bool visit(EnumValue const& _node) { return visitNode(_node); }
virtual bool visit(ParameterList const& _node) { return visitNode(_node); }
@ -248,6 +251,7 @@ public:
virtual void endVisit(IdentifierPath const& _node) { endVisitNode(_node); }
virtual void endVisit(InheritanceSpecifier const& _node) { endVisitNode(_node); }
virtual void endVisit(UsingForDirective const& _node) { endVisitNode(_node); }
virtual void endVisit(UserDefinedValueTypeDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(StructDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(EnumDefinition const& _node) { endVisitNode(_node); }
virtual void endVisit(EnumValue const& _node) { endVisitNode(_node); }

View File

@ -164,6 +164,26 @@ void EnumValue::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this);
}
void UserDefinedValueTypeDefinition::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
if (m_underlyingType)
m_underlyingType->accept(_visitor);
}
_visitor.endVisit(*this);
}
void UserDefinedValueTypeDefinition::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
{
if (m_underlyingType)
m_underlyingType->accept(_visitor);
}
_visitor.endVisit(*this);
}
void UsingForDirective::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))

View File

@ -566,9 +566,10 @@ MagicType const* TypeProvider::meta(Type const* _type)
solAssert(
_type && (
_type->category() == Type::Category::Contract ||
_type->category() == Type::Category::Integer
_type->category() == Type::Category::Integer ||
_type->category() == Type::Category::Enum
),
"Only contracts or integer types supported for now."
"Only enum, contracts or integer types supported for now."
);
return createAndGet<MagicType>(_type);
}
@ -577,3 +578,8 @@ MappingType const* TypeProvider::mapping(Type const* _keyType, Type const* _valu
{
return createAndGet<MappingType>(_keyType, _valueType);
}
UserDefinedValueType const* TypeProvider::userDefinedValueType(UserDefinedValueTypeDefinition const& _definition)
{
return createAndGet<UserDefinedValueType>(_definition);
}

View File

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

View File

@ -1135,7 +1135,7 @@ IntegerType const* RationalNumberType::integerType() const
return nullptr;
else
return TypeProvider::integer(
max(util::bytesRequired(value), 1u) * 8,
max(numberEncodingSize(value), 1u) * 8,
negative ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned
);
}
@ -1169,7 +1169,7 @@ FixedPointType const* RationalNumberType::fixedPointType() const
if (v > u256(-1))
return nullptr;
unsigned totalBits = max(util::bytesRequired(v), 1u) * 8;
unsigned totalBits = max(numberEncodingSize(v), 1u) * 8;
solAssert(totalBits <= 256, "");
return TypeProvider::fixedPoint(
@ -2537,6 +2537,37 @@ unsigned EnumType::memberValue(ASTString const& _member) const
solAssert(false, "Requested unknown enum value " + _member);
}
Type const& UserDefinedValueType::underlyingType() const
{
Type const* type = m_definition.underlyingType()->annotation().type;
solAssert(type, "");
solAssert(type->category() != Category::UserDefinedValueType, "");
return *type;
}
string UserDefinedValueType::richIdentifier() const
{
return "t_userDefinedValueType" + parenthesizeIdentifier(m_definition.name()) + to_string(m_definition.id());
}
bool UserDefinedValueType::operator==(Type const& _other) const
{
if (_other.category() != category())
return false;
UserDefinedValueType const& other = dynamic_cast<UserDefinedValueType const&>(_other);
return other.definition() == definition();
}
string UserDefinedValueType::toString(bool /* _short */) const
{
return *definition().annotation().canonicalName;
}
vector<tuple<string, Type const*>> UserDefinedValueType::makeStackItems() const
{
return underlyingType().stackItems();
}
BoolResult TupleType::isImplicitlyConvertibleTo(Type const& _other) const
{
if (auto tupleType = dynamic_cast<TupleType const*>(&_other))
@ -2888,6 +2919,8 @@ string FunctionType::richIdentifier() const
case Kind::GasLeft: id += "gasleft"; break;
case Kind::Event: id += "event"; break;
case Kind::Error: id += "error"; break;
case Kind::Wrap: id += "wrap"; break;
case Kind::Unwrap: id += "unwrap"; break;
case Kind::SetGas: id += "setgas"; break;
case Kind::SetValue: id += "setvalue"; break;
case Kind::BlockHash: id += "blockhash"; break;
@ -3043,10 +3076,7 @@ u256 FunctionType::storageSize() const
bool FunctionType::leftAligned() const
{
if (m_kind == Kind::External)
return true;
else
solAssert(false, "Alignment property of non-exportable function type requested.");
return m_kind == Kind::External;
}
unsigned FunctionType::storageBytes() const
@ -3471,7 +3501,9 @@ bool FunctionType::isPure() const
m_kind == Kind::ABIEncodeWithSelector ||
m_kind == Kind::ABIEncodeWithSignature ||
m_kind == Kind::ABIDecode ||
m_kind == Kind::MetaType;
m_kind == Kind::MetaType ||
m_kind == Kind::Wrap ||
m_kind == Kind::Unwrap;
}
TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)
@ -3758,6 +3790,34 @@ MemberList::MemberMap TypeType::nativeMembers(ASTNode const* _currentScope) cons
for (ASTPointer<EnumValue> const& enumValue: enumDef.members())
members.emplace_back(enumValue.get(), enumType);
}
else if (m_actualType->category() == Category::UserDefinedValueType)
{
auto& userDefined = dynamic_cast<UserDefinedValueType const&>(*m_actualType);
members.emplace_back(
"wrap",
TypeProvider::function(
TypePointers{&userDefined.underlyingType()},
TypePointers{&userDefined},
strings{string{}},
strings{string{}},
FunctionType::Kind::Wrap,
false, /*_arbitraryParameters */
StateMutability::Pure
)
);
members.emplace_back(
"unwrap",
TypeProvider::function(
TypePointers{&userDefined},
TypePointers{&userDefined.underlyingType()},
strings{string{}},
strings{string{}},
FunctionType::Kind::Unwrap,
false, /* _arbitraryParameters */
StateMutability::Pure
)
);
}
else if (
auto const* arrayType = dynamic_cast<ArrayType const*>(m_actualType);
arrayType && arrayType->isByteArray()
@ -3965,9 +4025,10 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
solAssert(
m_typeArgument && (
m_typeArgument->category() == Type::Category::Contract ||
m_typeArgument->category() == Type::Category::Integer
m_typeArgument->category() == Type::Category::Integer ||
m_typeArgument->category() == Type::Category::Enum
),
"Only contracts or integer types supported for now"
"Only enums, contracts or integer types supported for now"
);
if (m_typeArgument->category() == Type::Category::Contract)
@ -3993,6 +4054,14 @@ MemberList::MemberMap MagicType::nativeMembers(ASTNode const*) const
{"max", integerTypePointer},
});
}
else if (m_typeArgument->category() == Type::Category::Enum)
{
EnumType const* enumTypePointer = dynamic_cast<EnumType const*>(m_typeArgument);
return MemberList::MemberMap({
{"min", enumTypePointer},
{"max", enumTypePointer},
});
}
}
}
solAssert(false, "Unknown kind of magic.");

View File

@ -29,6 +29,7 @@
#include <liblangutil/Exceptions.h>
#include <libsolutil/Common.h>
#include <libsolutil/Numeric.h>
#include <libsolutil/CommonIO.h>
#include <libsolutil/LazyInit.h>
#include <libsolutil/Result.h>
@ -174,7 +175,7 @@ public:
enum class Category
{
Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, ArraySlice,
FixedBytes, Contract, Struct, Function, Enum, Tuple,
FixedBytes, Contract, Struct, Function, Enum, UserDefinedValueType, Tuple,
Mapping, TypeType, Modifier, Magic, Module,
InaccessibleDynamic
};
@ -1071,11 +1072,85 @@ public:
/// @returns the value that the string has in the Enum
unsigned int memberValue(ASTString const& _member) const;
size_t numberOfMembers() const;
unsigned int minValue() const { return 0; }
unsigned int maxValue() const
{
solAssert(numberOfMembers() <= 256, "");
return static_cast<unsigned int>(numberOfMembers()) - 1;
}
private:
EnumDefinition const& m_enum;
};
/**
* The type of a UserDefinedValueType.
*/
class UserDefinedValueType: public Type
{
public:
explicit UserDefinedValueType(UserDefinedValueTypeDefinition const& _definition):
m_definition(_definition)
{}
Category category() const override { return Category::UserDefinedValueType; }
Type const& underlyingType() const;
UserDefinedValueTypeDefinition const& definition() const { return m_definition; }
TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; }
Type const* encodingType() const override { return &underlyingType(); }
TypeResult interfaceType(bool /* _inLibrary */) const override {return &underlyingType(); }
std::string richIdentifier() const override;
bool operator==(Type const& _other) const override;
unsigned calldataEncodedSize(bool _padded) const override { return underlyingType().calldataEncodedSize(_padded); }
bool leftAligned() const override { return underlyingType().leftAligned(); }
bool canBeStored() const override { return underlyingType().canBeStored(); }
u256 storageSize() const override { return underlyingType().storageSize(); }
unsigned storageBytes() const override { return underlyingType().storageBytes(); }
bool isValueType() const override
{
solAssert(underlyingType().isValueType(), "");
return true;
}
bool nameable() const override
{
solAssert(underlyingType().nameable(), "");
return true;
}
bool containsNestedMapping() const override
{
solAssert(nameable(), "Called for a non nameable type.");
solAssert(!underlyingType().containsNestedMapping(), "");
return false;
}
bool hasSimpleZeroValueInMemory() const override
{
solAssert(underlyingType().hasSimpleZeroValueInMemory(), "");
return true;
}
bool dataStoredIn(DataLocation _loc) const override
{
solAssert(!underlyingType().dataStoredIn(_loc), "");
return false;
}
std::string toString(bool _short) const override;
std::string canonicalName() const override { solAssert(false, ""); }
std::string signatureInExternalFunction(bool) const override { solAssert(false, ""); }
protected:
std::vector<std::tuple<std::string, Type const*>> makeStackItems() const override;
private:
UserDefinedValueTypeDefinition const& m_definition;
};
/**
* Type that can hold a finite sequence of values of different types.
* In some cases, the components are empty pointers (when used as placeholders).
@ -1144,6 +1219,8 @@ public:
RIPEMD160, ///< CALL to special contract for ripemd160
Event, ///< syntactic sugar for LOG*
Error, ///< creating an error instance in revert or require
Wrap, ///< customType.wrap(...) for user defined value types
Unwrap, ///< customType.unwrap(...) for user defined value types
SetGas, ///< modify the default gas value for the function call
SetValue, ///< modify the default value transfer for the function call
BlockHash, ///< BLOCKHASH

View File

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

View File

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

View File

@ -62,10 +62,6 @@ public:
std::string generatedYulUtilityCode() const { return m_context.generatedYulUtilityCode(); }
std::string runtimeGeneratedYulUtilityCode() const { return m_runtimeContext.generatedYulUtilityCode(); }
/// @returns the entry label of the given function. Might return an AssemblyItem of type
/// UndefinedItem if it does not exist yet.
evmasm::AssemblyItem functionEntryLabel(FunctionDefinition const& _function) const;
private:
OptimiserSettings const m_optimiserSettings;
CompilerContext m_runtimeContext;

View File

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

View File

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

View File

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

View File

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

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