Merge pull request #9126 from ethereum/develop

Merge develop into release for 0.6.9
This commit is contained in:
chriseth 2020-06-04 17:27:00 +02:00 committed by GitHub
commit 3e3065ac00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
469 changed files with 7780 additions and 3049 deletions

View File

@ -12,10 +12,10 @@ parameters:
default: "4"
ubuntu-2004-docker-image-rev:
type: string
default: "1"
default: "2"
ubuntu-2004-clang-docker-image-rev:
type: string
default: "1"
default: "2"
ubuntu-1604-clang-ossfuzz-docker-image-rev:
type: string
default: "2"
@ -173,6 +173,18 @@ defaults:
- store_test_results: *store_test_results
- store_artifacts: *artifacts_test_results
- test_asan_clang: &test_asan_clang
<<: *test_ubuntu2004_clang
steps:
- checkout
- attach_workspace:
at: build
- run:
<<: *run_soltest
no_output_timeout: 30m
- store_test_results: *store_test_results
- store_artifacts: *artifacts_test_results
# --------------------------------------------------------------------------
# Workflow Templates
@ -216,6 +228,11 @@ defaults:
requires:
- b_ubu_asan
- workflow_ubuntu2004_asan_clang: &workflow_ubuntu2004_asan_clang
<<: *workflow_trigger_on_tags
requires:
- b_ubu_asan_clang
- workflow_emscripten: &workflow_emscripten
<<: *workflow_trigger_on_tags
requires:
@ -299,6 +316,15 @@ jobs:
name: checking shell scripts
command: ./scripts/chk_shellscripts/chk_shellscripts.sh
chk_errorcodes:
docker:
- image: circleci/python:3.6
steps:
- checkout
- run:
name: Check for error codes
command: ./scripts/fix_error_ids.py --check-only
chk_pylint:
docker:
- image: buildpack-deps:eoan
@ -380,6 +406,20 @@ jobs:
- store_artifacts: *artifacts_solc
- persist_to_workspace: *artifacts_executables
b_ubu_asan_clang: &build_ubuntu2004_clang
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu2004-clang-<< pipeline.parameters.ubuntu-2004-clang-docker-image-rev >>
environment:
CC: clang
CXX: clang++
CMAKE_OPTIONS: -DSANITIZE=address
steps:
- checkout
- run: *run_build
- store_artifacts: *artifacts_solc
- persist_to_workspace: *artifacts_executables
b_ubu: &build_ubuntu2004
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu2004-<< pipeline.parameters.ubuntu-2004-docker-image-rev >>
@ -500,7 +540,7 @@ jobs:
xcode: "11.0.0"
environment:
TERM: xterm
CMAKE_BUILD_TYPE: Debug
CMAKE_BUILD_TYPE: Release
steps:
- checkout
- restore_cache:
@ -558,27 +598,15 @@ jobs:
b_ems:
docker:
- image: trzeci/emscripten:sdk-tag-1.39.3-64bit
- image: ethereum/solidity-buildpack-deps:emsdk-1.39.15-2
environment:
TERM: xterm
steps:
- checkout
- restore_cache:
name: Restore Boost build
key: &boost-cache-key emscripten-boost-{{ checksum "scripts/travis-emscripten/install_deps.sh" }}{{ checksum "scripts/build_emscripten.sh" }}{{ checksum "scripts/travis-emscripten/build_emscripten.sh" }}
- run:
name: Bootstrap Boost
command: |
scripts/travis-emscripten/install_deps.sh
- run:
name: Build
command: |
scripts/travis-emscripten/build_emscripten.sh
- save_cache:
name: Save Boost build
key: *boost-cache-key
paths:
- boost_1_70_0_install
- store_artifacts:
path: emscripten_build/libsolc/soljson.js
destination: soljson.js
@ -684,6 +712,14 @@ jobs:
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:
<<: *test_asan_clang
environment:
EVM: constantinople
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_ems_solcjs:
docker:
- image: buildpack-deps:latest
@ -818,6 +854,7 @@ workflows:
- chk_buglist: *workflow_trigger_on_tags
- chk_proofs: *workflow_trigger_on_tags
- chk_pylint: *workflow_trigger_on_tags
- chk_errorcodes: *workflow_trigger_on_tags
- chk_antlr_grammar: *workflow_trigger_on_tags
- chk_docs_pragma_min_version: *workflow_trigger_on_tags
@ -848,7 +885,9 @@ 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_cli: *workflow_ubuntu2004_asan
# Emscripten build and selected tests

View File

@ -0,0 +1,65 @@
# vim:syntax=dockerfile
#------------------------------------------------------------------------------
# Dockerfile for building and testing Solidity Compiler on CI
# Target: Emscripten
# URL: https://hub.docker.com/r/ethereum/solidity-buildpack-deps
#
# This file is part of solidity.
#
# solidity is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# solidity is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with solidity. If not, see <http://www.gnu.org/licenses/>
#
# (c) 2016-2019 solidity contributors.
#------------------------------------------------------------------------------
#
# The Emscripten SDK at https://github.com/emscripten-core/emsdk/
# contains a Makefile in the docker/ subdirectory that can be used to create the
# required base image using:
#
# make version=1.39.15 build
#
FROM emscripten/emsdk:1.39.15 AS base
ADD emscripten.jam /usr/src
RUN set -ex; \
cd /usr/src; \
git clone https://github.com/Z3Prover/z3.git -b z3-4.8.8 --depth 1 ; \
cd z3; \
mkdir build; \
cd build; \
emcmake cmake \
-DCMAKE_BUILD_TYPE=MinSizeRel \
-DCMAKE_INSTALL_PREFIX=/emsdk/emscripten/sdk/system/ \
-DZ3_BUILD_LIBZ3_SHARED=OFF \
-DZ3_ENABLE_EXAMPLE_TARGETS=OFF \
-DZ3_BUILD_TEST_EXECUTABLES=OFF \
-DZ3_BUILD_EXECUTABLE=OFF \
-DZ3_SINGLE_THREADED=ON \
-DCMAKE_CXX_FLAGS="-s DISABLE_EXCEPTION_CATCHING=0" \
..; \
make; make install; \
rm -r /usr/src/z3; \
cd /usr/src; \
wget -q 'https://dl.bintray.com/boostorg/release/1.73.0/source/boost_1_73_0.tar.bz2' -O boost.tar.bz2; \
test "$(sha256sum boost.tar.bz2)" = "4eb3b8d442b426dc35346235c8733b5ae35ba431690e38c6a8263dce9fcbb402 boost.tar.bz2"; \
tar -xf boost.tar.bz2; \
rm boost.tar.bz2; \
cd boost_1_73_0; \
mv ../emscripten.jam .; \
./bootstrap.sh; \
echo "using emscripten : : em++ ;" >> project-config.jam ; \
./b2 toolset=emscripten link=static variant=release threading=single runtime-link=static \
--with-system --with-filesystem --with-test --with-program_options \
cxxflags="-s DISABLE_EXCEPTION_CATCHING=0 -Wno-unused-local-typedef -Wno-variadic-macros -Wno-c99-extensions -Wno-all" \
--prefix=/emsdk/emscripten/sdk/system install; \
rm -r /usr/src/boost_1_73_0

View File

@ -26,6 +26,9 @@ FROM buildpack-deps:focal AS base
ARG DEBIAN_FRONTEND=noninteractive
RUN set -ex; \
dist=$(grep DISTRIB_CODENAME /etc/lsb-release | cut -d= -f2); \
echo "deb http://ppa.launchpad.net/ethereum/cpp-build-deps/ubuntu $dist main" >> /etc/apt/sources.list ; \
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1c52189c923f6ca9 ; \
apt-get update; \
apt-get install -qqy --no-install-recommends \
build-essential \
@ -33,7 +36,7 @@ RUN set -ex; \
cmake ninja-build \
libboost-filesystem-dev libboost-test-dev libboost-system-dev \
libboost-program-options-dev \
libcvc4-dev z3 libz3-dev \
libcvc4-dev libz3-static-dev \
; \
apt-get install -qy python3-pip python3-sphinx; \
pip3 install codecov; \

View File

@ -26,6 +26,9 @@ FROM buildpack-deps:focal AS base
ARG DEBIAN_FRONTEND=noninteractive
RUN set -ex; \
dist=$(grep DISTRIB_CODENAME /etc/lsb-release | cut -d= -f2); \
echo "deb http://ppa.launchpad.net/ethereum/cpp-build-deps/ubuntu $dist main" >> /etc/apt/sources.list ; \
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1c52189c923f6ca9 ; \
apt-get update; \
apt-get install -qqy --no-install-recommends \
build-essential \
@ -33,8 +36,8 @@ RUN set -ex; \
cmake ninja-build \
libboost-filesystem-dev libboost-test-dev libboost-system-dev \
libboost-program-options-dev \
clang llvm-dev \
z3 libz3-dev \
clang \
libz3-static-dev \
; \
rm -rf /var/lib/apt/lists/*

View File

@ -79,10 +79,10 @@ rule init ( version ? : command * : options * )
# @todo this seems to be the right way, but this is a list somehow
toolset.add-requirements <toolset>emscripten:<testing.launcher>node ;
toolset.flags emscripten.compile STDHDRS $(condition) : /emsdk_portable/emscripten/sdk/system/include ;
toolset.flags emscripten.link STDLIBPATH $(condition) : /emsdk_portable/emscripten/sdk/system/lib ;
toolset.flags emscripten AR $(condition) : /emsdk_portable/emscripten/sdk/emar ;
toolset.flags emscripten RANLIB $(condition) : /emsdk_portable/emscripten/sdk/emranlib ;
toolset.flags emscripten.compile STDHDRS $(condition) : /emsdk/emscripten/sdk/system/include ;
toolset.flags emscripten.link STDLIBPATH $(condition) : /emsdk/emscripten/sdk/system/lib ;
toolset.flags emscripten AR $(condition) : /emsdk/emscripten/sdk/emar ;
toolset.flags emscripten RANLIB $(condition) : /emsdk/emscripten/sdk/emranlib ;
}
type.set-generated-target-suffix EXE : <toolset>emscripten : js ;

View File

@ -43,13 +43,13 @@ then
./scripts/install_obsolete_jsoncpp_1_7_4.sh
# z3
wget https://github.com/Z3Prover/z3/releases/download/z3-4.8.7/z3-4.8.7-x64-osx-10.14.6.zip
unzip z3-4.8.7-x64-osx-10.14.6.zip
rm -f z3-4.8.7-x64-osx-10.14.6.zip
cp z3-4.8.7-x64-osx-10.14.6/bin/libz3.a /usr/local/lib
cp z3-4.8.7-x64-osx-10.14.6/bin/z3 /usr/local/bin
cp z3-4.8.7-x64-osx-10.14.6/include/* /usr/local/include
rm -rf z3-4.8.7-x64-osx-10.14.6
wget https://github.com/Z3Prover/z3/releases/download/z3-4.8.8/z3-4.8.8-x64-osx-10.14.6.zip
unzip z3-4.8.8-x64-osx-10.14.6.zip
rm -f z3-4.8.8-x64-osx-10.14.6.zip
cp z3-4.8.8-x64-osx-10.14.6/bin/libz3.a /usr/local/lib
cp z3-4.8.8-x64-osx-10.14.6/bin/z3 /usr/local/bin
cp z3-4.8.8-x64-osx-10.14.6/include/* /usr/local/include
rm -rf z3-4.8.8-x64-osx-10.14.6
# evmone
wget https://github.com/ethereum/evmone/releases/download/v0.4.0/evmone-0.4.0-darwin-x86_64.tar.gz

View File

@ -110,7 +110,7 @@ matrix:
before_install:
- nvm install 8
- nvm use 8
- docker pull trzeci/emscripten:sdk-tag-1.39.3-64bit
- docker pull ethereum/solidity-buildpack-deps:emsdk-1.39.15-1
env:
- SOLC_EMSCRIPTEN=On
- SOLC_INSTALL_DEPS_TRAVIS=Off

View File

@ -10,7 +10,7 @@ include(EthPolicy)
eth_policy()
# project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.6.8")
set(PROJECT_VERSION "0.6.9")
# OSX target needed in order to support std::visit
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
@ -51,8 +51,26 @@ configure_file("${CMAKE_SOURCE_DIR}/cmake/templates/license.h.in" include/licens
include(EthOptions)
configure_project(TESTS)
find_package(Z3 4.6.0)
if (${Z3_FOUND})
add_definitions(-DHAVE_Z3)
message("Z3 SMT solver found. This enables optional SMT checking with Z3.")
endif()
find_package(CVC4 QUIET)
if (${CVC4_FOUND})
add_definitions(-DHAVE_CVC4)
message("CVC4 SMT solver found. This enables optional SMT checking with CVC4.")
endif()
if (NOT (${Z3_FOUND} OR ${CVC4_FOUND}))
message("No SMT solver found (or it has been forcefully disabled). Optional SMT checking will not be available.\
\nPlease install Z3 or CVC4 or remove the option disabling them (USE_Z3, USE_CVC4).")
endif()
add_subdirectory(libsolutil)
add_subdirectory(liblangutil)
add_subdirectory(libsmtutil)
add_subdirectory(libevmasm)
add_subdirectory(libyul)
add_subdirectory(libsolidity)

View File

@ -1,3 +1,40 @@
### 0.6.9 (2020-06-04)
Language Features:
* Permit calldata location for all variables.
* NatSpec: Support NatSpec comments on state variables.
* Yul: EVM instruction `pc()` is marked deprecated and will be removed in the next breaking release.
Compiler Features:
* Build system: Update the soljson.js build to emscripten 1.39.15 and boost 1.73.0 and include Z3 for integrated SMTChecker support without the callback mechanism.
* Build system: Switch the emscripten build from the fastcomp backend to the upstream backend.
* Code Generator: Do not introduce new internal source references for small compiler routines.
* Commandline Interface: Adds new option ``--base-path PATH`` to use the given path as the root of the source tree (defaults to the root of the filesystem).
* SMTChecker: Support array ``length``.
* SMTChecker: Support array ``push`` and ``pop``.
* SMTChecker: General support to BitVectors and the bitwise ``and`` operator.
Bugfixes:
* Code Generator: Trigger proper unimplemented errors on certain array copy operations.
* Commandline Interface: Fix internal error when using ``--assemble`` or ``--yul`` options with ``--machine ewasm`` but without specifying ``--yul-dialect``.
* NatSpec: DocString block is terminated when encountering an empty line.
* Optimizer: Fixed a bug in BlockDeDuplicator.
* Scanner: Fix bug when two empty NatSpec comments lead to scanning past EOL.
* SMTChecker: Fix internal error on try/catch clauses with parameters.
* SMTChecker: Fix internal error when applying arithmetic operators to fixed point variables.
* SMTChecker: Fix internal error when assigning to index access inside branches.
* SMTChecker: Fix internal error when short circuiting Boolean expressions with function calls in state variable initialization.
* Type Checker: Disallow assignments to storage variables of type ``mapping``.
* Type Checker: Disallow inline arrays of non-nameable types.
* Type Checker: Disallow usage of override with non-public state variables.
* Type Checker: Fix internal compiler error when accessing members of array slices.
* Type Checker: Fix internal compiler error when forward referencing non-literal constants from inline assembly.
* Type Checker: Fix internal compiler error when trying to decode too large static arrays.
* Type Checker: Fix wrong compiler error when referencing an overridden function without calling it.
### 0.6.8 (2020-05-14)
Important Bugfixes:

View File

@ -16,9 +16,11 @@
include(EthCheckCXXCompilerFlag)
eth_add_cxx_compiler_flag_if_supported(-fstack-protector-strong have_stack_protector_strong_support)
if(NOT have_stack_protector_strong_support)
eth_add_cxx_compiler_flag_if_supported(-fstack-protector)
if(NOT EMSCRIPTEN)
eth_add_cxx_compiler_flag_if_supported(-fstack-protector-strong have_stack_protector_strong_support)
if(NOT have_stack_protector_strong_support)
eth_add_cxx_compiler_flag_if_supported(-fstack-protector)
endif()
endif()
eth_add_cxx_compiler_flag_if_supported(-Wimplicit-fallthrough)
@ -109,15 +111,13 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA
# Re-enable exception catching (optimisations above -O1 disable it)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s DISABLE_EXCEPTION_CATCHING=0")
# Remove any code related to exit (such as atexit)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s NO_EXIT_RUNTIME=1")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXIT_RUNTIME=0")
# Remove any code related to filesystem access
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s NO_FILESYSTEM=1")
# Remove variables even if it needs to be duplicated (can improve speed at the cost of size)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s AGGRESSIVE_VARIABLE_ELIMINATION=1")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s FILESYSTEM=0")
# Allow memory growth, but disable some optimisations
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s ALLOW_MEMORY_GROWTH=1")
# Disable eval()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s NO_DYNAMIC_EXECUTION=1")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s DYNAMIC_EXECUTION=0")
# Disable greedy exception catcher
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s NODEJS_CATCH_EXIT=0")
# Abort if linking results in any undefined symbols

View File

@ -1,7 +1,8 @@
# Inherit default options
include("${CMAKE_CURRENT_LIST_DIR}/default.cmake")
# Disable CVC4.
# Disable CVC4 and Z3.
set(USE_CVC4 OFF CACHE BOOL "Disable CVC4" FORCE)
set(USE_Z3 OFF CACHE BOOL "Disable Z3" FORCE)
# Enable fuzzers
set(OSSFUZZ ON CACHE BOOL "Enable fuzzer build" FORCE)
set(LIB_FUZZING_ENGINE $ENV{LIB_FUZZING_ENGINE} CACHE STRING "Use fuzzer back-end defined by environment variable" FORCE)

View File

@ -16,8 +16,8 @@ This section lists changes where the behaviour of your code might
change without the compiler telling you about it.
* The resulting type of an exponentiation is the type of the base. It used to be the smallest type
that can hold both the type of the base and the type of the exponent, as with symmentric
operations. Additionally, signed types are allowed for the base of the exponetation.
that can hold both the type of the base and the type of the exponent, as with symmetric
operations. Additionally, signed types are allowed for the base of the exponentiation.
Explicitness Requirements
@ -36,9 +36,9 @@ For most of the topics the compiler will provide suggestions.
like so: ``override(Base1, Base2)``.
* Member-access to ``length`` of arrays is now always read-only, even for storage arrays. It is no
longer possible to resize storage arrays assigning a new value to their length. Use ``push()``,
``push(value)`` or ``pop()`` instead, or assign a full array, which will of course overwrite existing content.
The reason behind this is to prevent storage collisions by gigantic
longer possible to resize storage arrays by assigning a new value to their length. Use ``push()``,
``push(value)`` or ``pop()`` instead, or assign a full array, which will of course overwrite the existing content.
The reason behind this is to prevent storage collisions of gigantic
storage arrays.
* The new keyword ``abstract`` can be used to mark contracts as abstract. It has to be used
@ -86,7 +86,7 @@ New Features
============
This section lists things that were not possible prior to Solidity 0.6.0
or at least were more difficult to achieve prior to Solidity 0.6.0.
or were more difficult to achieve.
* The :ref:`try/catch statement <try-catch>` allows you to react on failed external calls.
* ``struct`` and ``enum`` types can be declared at file level.
@ -103,7 +103,7 @@ Interface Changes
This section lists changes that are unrelated to the language itself, but that have an effect on the interfaces of
the compiler. These may change the way how you use the compiler on the command line, how you use its programmable
interface or how you analyze the output produced by it.
interface, or how you analyze the output produced by it.
New Error Reporter
~~~~~~~~~~~~~~~~~~
@ -166,7 +166,7 @@ This section gives detailed instructions on how to update prior code for every b
documentation so long as the notices are in the order they appear in the tuple return type.
* Choose unique identifiers for variable declarations in inline assembly that do not conflict
with declartions outside the inline assembly block.
with declarations outside the inline assembly block.
* Add ``virtual`` to every non-interface function you intend to override. Add ``virtual``
to all functions without implementation outside interfaces. For single inheritance, add

View File

@ -1163,5 +1163,9 @@
"0.6.8": {
"bugs": [],
"released": "2020-05-14"
},
"0.6.9": {
"bugs": [],
"released": "2020-06-04"
}
}

View File

@ -76,7 +76,7 @@ are initialized with their :ref:`default value <default-value>` and have that
value until they are (re-)assigned.
You can either explicitly assign to return variables and
then leave the function using ``return;``,
then leave the function as above,
or you can provide return values
(either a single or :ref:`multiple ones<multi-return>`) directly with the ``return``
statement::
@ -94,8 +94,8 @@ statement::
}
}
This form is equivalent to first assigning values to the
return variables and then using ``return;`` to leave the function.
If you use an early ``return`` to leave a function that has return variables,
you must provide return values together with the return statement.
.. note::
You cannot return some types from non-internal functions, notably

View File

@ -255,9 +255,9 @@ which only need to be created if there is a dispute.
contract C {
function createDSalted(bytes32 salt, uint arg) public {
/// This complicated expression just tells you how the address
/// can be pre-computed. It is just there for illustration.
/// You actually only need ``new D{salt: salt}(arg)``.
// This complicated expression just tells you how the address
// can be pre-computed. It is just there for illustration.
// You actually only need ``new D{salt: salt}(arg)``.
address predictedAddress = address(uint(keccak256(abi.encodePacked(
byte(0xff),
address(this),

View File

@ -28,6 +28,9 @@ Documentation Example
Documentation is inserted above each ``class``, ``interface`` and
``function`` using the doxygen notation format.
Note: a ``public`` state variable is equivalent to a ``function``
for the purposes of NatSpec.
- For Solidity you may choose ``///`` for single or multi-line
comments, or ``/**`` and ending with ``*/``.
@ -82,10 +85,10 @@ Tag
=========== =============================================================================== =============================
``@title`` A title that should describe the contract/interface contract, interface
``@author`` The name of the author contract, interface, function
``@notice`` Explain to an end user what this does contract, interface, function
``@dev`` Explain to a developer any extra details contract, interface, function
``@notice`` Explain to an end user what this does contract, interface, function, public state variable
``@dev`` Explain to a developer any extra details contract, interface, function, state variable
``@param`` Documents a parameter just like in doxygen (must be followed by parameter name) function
``@return`` Documents the return variables of a contract's function function
``@return`` Documents the return variables of a contract's function function, public state variable
=========== =============================================================================== =============================
If your function returns multiple values, like ``(int quotient, int remainder)``

View File

@ -63,7 +63,7 @@ complete contract):
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract Fund {
/// Mapping of ether shares of the contract.
/// @dev Mapping of ether shares of the contract.
mapping(address => uint) shares;
/// Withdraw your share.
function withdraw() public {
@ -87,7 +87,7 @@ as it uses ``call`` which forwards all remaining gas by default:
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract Fund {
/// Mapping of ether shares of the contract.
/// @dev Mapping of ether shares of the contract.
mapping(address => uint) shares;
/// Withdraw your share.
function withdraw() public {
@ -106,7 +106,7 @@ outlined further below:
pragma solidity >=0.4.11 <0.7.0;
contract Fund {
/// Mapping of ether shares of the contract.
/// @dev Mapping of ether shares of the contract.
mapping(address => uint) shares;
/// Withdraw your share.
function withdraw() public {
@ -657,3 +657,14 @@ Notice that we do not clear knowledge about ``array`` and ``d`` because they
are located in storage, even though they also have type ``uint[]``. However,
if ``d`` was assigned, we would need to clear knowledge about ``array`` and
vice-versa.
Real World Assumptions
======================
Some scenarios can be expressed in Solidity and the EVM, but are expected to
never occur in practice.
One of such cases is the length of a dynamic storage array overflowing during a
push: If the ``push`` operation is applied to an array of length 2^256 - 1, its
length silently overflows.
However, this is unlikely to happen in practice, since the operations required
to grow the array to that point would take billions of years to execute.

View File

@ -13,8 +13,7 @@ arrays and mappings. If you use a reference type, you always have to explicitly
provide the data area where the type is stored: ``memory`` (whose lifetime is limited
to an external function call), ``storage`` (the location where the state variables
are stored, where the lifetime is limited to the lifetime of a contract)
or ``calldata`` (special data location that contains the function arguments,
only available for external function call parameters).
or ``calldata`` (special data location that contains the function arguments).
An assignment or type conversion that changes the data location will always incur an automatic copy operation,
while assignments inside the same data location only copy in some cases for storage types.
@ -26,9 +25,9 @@ Data location
Every reference type has an additional
annotation, the "data location", about where it is stored. There are three data locations:
``memory``, ``storage`` and ``calldata``. Calldata is only valid for parameters of external contract
functions and is required for this type of parameter. Calldata is a non-modifiable,
``memory``, ``storage`` and ``calldata``. Calldata is a non-modifiable,
non-persistent area where function arguments are stored, and behaves mostly like memory.
It is required for parameters of external functions but can also be used for other variables.
.. note::
@ -36,6 +35,12 @@ non-persistent area where function arguments are stored, and behaves mostly like
depending on the kind of variable, function type, etc., but all complex types must now give an explicit
data location.
.. note::
If you can, try to use ``calldata`` as data location because it will avoid copies and
also makes sure that the data cannot be modified. Arrays and structs with ``calldata``
data location can also be returned from functions, but it is not possible to
allocate such types.
.. _data-location-assignment:
Data location and assignment behaviour
@ -415,7 +420,7 @@ Array slices are useful to ABI-decode secondary data passed in function paramete
pragma solidity >=0.6.0 <0.7.0;
contract Proxy {
/// Address of the client contract managed by proxy i.e., this contract
/// @dev Address of the client contract managed by proxy i.e., this contract
address client;
constructor(address _client) public {

View File

@ -44,8 +44,15 @@ An empty remapping prefix is not allowed.
If there are multiple matches due to remappings, the one with the longest common prefix is selected.
When accessing the filesystem to search for imports, all paths are treated as if they were fully qualified paths.
This behaviour can be customized by adding the command line option ``--base-path`` with a path to be prepended
before each filesystem access for imports is performed. Furthermore, the part added via ``--base-path``
will not appear in the contract metadata.
For security reasons the compiler has restrictions what directories it can access. Paths (and their subdirectories) of source files specified on the commandline and paths defined by remappings are allowed for import statements, but everything else is rejected. Additional paths (and their subdirectories) can be allowed via the ``--allow-paths /sample/path,/another/sample/path`` switch.
Everything inside the path specified via ``--base-path`` is always allowed.
If your contracts use :ref:`libraries <libraries>`, you will notice that the bytecode contains substrings of the form ``__$53aea86b7d70b31448b230b20ae141a537$__``. These are placeholders for the actual library addresses.
The placeholder is a 34 character prefix of the hex encoding of the keccak256 hash of the fully qualified library name.
The bytecode file will also contain lines of the form ``// <placeholder> -> <fq library name>`` at the end to help
@ -58,6 +65,7 @@ Either add ``--libraries "file.sol:Math:0x12345678901234567890123456789012345678
If ``solc`` is called with the option ``--link``, all input files are interpreted to be unlinked binaries (hex-encoded) in the ``__$53aea86b7d70b31448b230b20ae141a537$__``-format given above and are linked in-place (if the input is read from stdin, it is written to stdout). All options except ``--libraries`` are ignored (including ``-o``) in this case.
If ``solc`` is called with the option ``--standard-json``, it will expect a JSON input (as explained below) on the standard input, and return a JSON output on the standard output. This is the recommended interface for more complex and especially automated uses. The process will always terminate in a "success" state and report any errors via the JSON output.
The option ``--base-path`` is also processed in standard-json mode.
.. note::
The library placeholder used to be the fully qualified name of the library itself

View File

@ -41,7 +41,7 @@ using namespace solidity::util;
AssemblyItem const& Assembly::append(AssemblyItem const& _i)
{
assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow.");
m_deposit += _i.deposit();
m_deposit += static_cast<int>(_i.deposit());
m_items.emplace_back(_i);
if (!m_items.back().location().isValid() && m_currentSourceLocation.isValid())
m_items.back().setLocation(m_currentSourceLocation);
@ -77,10 +77,10 @@ string locationFromSources(StringMap const& _sourceCodes, SourceLocation const&
return "";
string const& source = it->second;
if (size_t(_location.start) >= source.size())
if (static_cast<size_t>(_location.start) >= source.size())
return "";
string cut = source.substr(_location.start, _location.end - _location.start);
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) + "...";
@ -106,7 +106,7 @@ public:
if (!(
_item.canBeFunctional() &&
_item.returnValues() <= 1 &&
_item.arguments() <= int(m_pending.size())
_item.arguments() <= m_pending.size()
))
{
flush();
@ -117,7 +117,7 @@ public:
if (_item.arguments() > 0)
{
expression += "(";
for (int i = 0; i < _item.arguments(); ++i)
for (size_t i = 0; i < _item.arguments(); ++i)
{
expression += m_pending.back();
m_pending.pop_back();
@ -225,12 +225,12 @@ Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices)
Json::Value& collection = root[".code"] = Json::arrayValue;
for (AssemblyItem const& i: m_items)
{
unsigned sourceIndex = unsigned(-1);
int sourceIndex = -1;
if (i.location().source)
{
auto iter = _sourceIndices.find(i.location().source->name());
if (iter != _sourceIndices.end())
sourceIndex = iter->second;
sourceIndex = static_cast<int>(iter->second);
}
switch (i.type())
@ -340,7 +340,7 @@ AssemblyItem Assembly::namedTag(string const& _name)
{
assertThrow(!_name.empty(), AssemblyException, "Empty named tag.");
if (!m_namedTags.count(_name))
m_namedTags[_name] = size_t(newTag().data());
m_namedTags[_name] = static_cast<size_t>(newTag().data());
return AssemblyItem{Tag, m_namedTags.at(_name)};
}
@ -435,13 +435,13 @@ map<u256, u256> Assembly::optimiseInternal(
// This only modifies PushTags, we have to run again to actually remove code.
if (_settings.runDeduplicate)
{
BlockDeduplicator dedup{m_items};
if (dedup.deduplicate())
BlockDeduplicator deduplicator{m_items};
if (deduplicator.deduplicate())
{
for (auto const& replacement: dedup.replacedTags())
for (auto const& replacement: deduplicator.replacedTags())
{
assertThrow(
replacement.first <= size_t(-1) && replacement.second <= size_t(-1),
replacement.first <= numeric_limits<size_t>::max() && replacement.second <= numeric_limits<size_t>::max(),
OptimizerException,
"Invalid tag replacement."
);
@ -451,8 +451,8 @@ map<u256, u256> Assembly::optimiseInternal(
"Replacement already known."
);
tagReplacements[replacement.first] = replacement.second;
if (_tagsReferencedFromOutside.erase(size_t(replacement.first)))
_tagsReferencedFromOutside.insert(size_t(replacement.second));
if (_tagsReferencedFromOutside.erase(static_cast<size_t>(replacement.first)))
_tagsReferencedFromOutside.insert(static_cast<size_t>(replacement.second));
}
count++;
}
@ -479,7 +479,7 @@ map<u256, u256> Assembly::optimiseInternal(
try
{
optimisedChunk = eliminator.getOptimizedItems();
shouldReplace = (optimisedChunk.size() < size_t(iter - orig));
shouldReplace = (optimisedChunk.size() < static_cast<size_t>(iter - orig));
}
catch (StackTooDeepException const&)
{
@ -544,7 +544,7 @@ LinkerObject const& Assembly::assemble() const
immutableReferencesBySub = linkerObject.immutableReferences;
}
for (size_t tagPos: sub->m_tagPositionsInBytecode)
if (tagPos != size_t(-1) && tagPos > subTagSize)
if (tagPos != numeric_limits<size_t>::max() && tagPos > subTagSize)
subTagSize = tagPos;
}
@ -567,7 +567,7 @@ LinkerObject const& Assembly::assemble() const
);
size_t bytesRequiredForCode = bytesRequired(subTagSize);
m_tagPositionsInBytecode = vector<size_t>(m_usedTags, -1);
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;
@ -586,7 +586,7 @@ LinkerObject const& Assembly::assemble() const
for (AssemblyItem const& i: m_items)
{
// store position of the invalid jump destination
if (i.type() != Tag && m_tagPositionsInBytecode[0] == size_t(-1))
if (i.type() != Tag && m_tagPositionsInBytecode[0] == numeric_limits<size_t>::max())
m_tagPositionsInBytecode[0] = ret.bytecode.size();
switch (i.type())
@ -629,15 +629,15 @@ LinkerObject const& Assembly::assemble() const
ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef);
break;
case PushSub:
assertThrow(i.data() <= size_t(-1), AssemblyException, "");
assertThrow(i.data() <= numeric_limits<size_t>::max(), AssemblyException, "");
ret.bytecode.push_back(dataRefPush);
subRef.insert(make_pair(size_t(i.data()), ret.bytecode.size()));
subRef.insert(make_pair(static_cast<size_t>(i.data()), ret.bytecode.size()));
ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef);
break;
case PushSubSize:
{
assertThrow(i.data() <= size_t(-1), AssemblyException, "");
auto s = m_subs.at(size_t(i.data()))->assemble().bytecode.size();
assertThrow(i.data() <= numeric_limits<size_t>::max(), AssemblyException, "");
auto s = m_subs.at(static_cast<size_t>(i.data()))->assemble().bytecode.size();
i.setPushedValue(u256(s));
uint8_t b = max<unsigned>(1, util::bytesRequired(s));
ret.bytecode.push_back((uint8_t)Instruction::PUSH1 - 1 + b);
@ -683,10 +683,10 @@ LinkerObject const& Assembly::assemble() const
break;
case Tag:
assertThrow(i.data() != 0, AssemblyException, "Invalid tag position.");
assertThrow(i.splitForeignPushTag().first == size_t(-1), AssemblyException, "Foreign tag.");
assertThrow(i.splitForeignPushTag().first == numeric_limits<size_t>::max(), AssemblyException, "Foreign tag.");
assertThrow(ret.bytecode.size() < 0xffffffffL, AssemblyException, "Tag too large.");
assertThrow(m_tagPositionsInBytecode[size_t(i.data())] == size_t(-1), AssemblyException, "Duplicate tag position.");
m_tagPositionsInBytecode[size_t(i.data())] = ret.bytecode.size();
assertThrow(m_tagPositionsInBytecode[static_cast<size_t>(i.data())] == numeric_limits<size_t>::max(), AssemblyException, "Duplicate tag position.");
m_tagPositionsInBytecode[static_cast<size_t>(i.data())] = ret.bytecode.size();
ret.bytecode.push_back((uint8_t)Instruction::JUMPDEST);
break;
default:
@ -722,14 +722,14 @@ LinkerObject const& Assembly::assemble() const
size_t subId;
size_t tagId;
tie(subId, tagId) = i.second;
assertThrow(subId == size_t(-1) || subId < m_subs.size(), AssemblyException, "Invalid sub id");
assertThrow(subId == numeric_limits<size_t>::max() || subId < m_subs.size(), AssemblyException, "Invalid sub id");
std::vector<size_t> const& tagPositions =
subId == size_t(-1) ?
subId == numeric_limits<size_t>::max() ?
m_tagPositionsInBytecode :
m_subs[subId]->m_tagPositionsInBytecode;
assertThrow(tagId < tagPositions.size(), AssemblyException, "Reference to non-existing tag.");
size_t pos = tagPositions[tagId];
assertThrow(pos != size_t(-1), AssemblyException, "Reference to tag without position.");
assertThrow(pos != numeric_limits<size_t>::max(), AssemblyException, "Reference to tag without position.");
assertThrow(util::bytesRequired(pos) <= bytesPerTag, AssemblyException, "Tag too large for reserved space.");
bytesRef r(ret.bytecode.data() + i.first, bytesPerTag);
toBigEndian(pos, r);

View File

@ -52,6 +52,7 @@ public:
AssemblyItem newSub(AssemblyPointer const& _sub) { m_subs.push_back(_sub); return AssemblyItem(PushSub, m_subs.size() - 1); }
Assembly const& sub(size_t _sub) const { return *m_subs.at(_sub); }
Assembly& sub(size_t _sub) { return *m_subs.at(_sub); }
size_t numSubs() const { return m_subs.size(); }
AssemblyItem newPushSubSize(u256 const& _subId) { return AssemblyItem(PushSubSize, _subId); }
AssemblyItem newPushLibraryAddress(std::string const& _identifier);
AssemblyItem newPushImmutable(std::string const& _identifier);
@ -96,6 +97,7 @@ public:
/// Changes the source location used for each appended item.
void setSourceLocation(langutil::SourceLocation const& _location) { m_currentSourceLocation = _location; }
langutil::SourceLocation const& currentSourceLocation() const { return m_currentSourceLocation; }
/// Assembles the assembly into bytecode. The assembly should not be modified after this call, since the assembled version is cached.
LinkerObject const& assemble() const;

View File

@ -34,7 +34,7 @@ AssemblyItem AssemblyItem::toSubAssemblyTag(size_t _subId) const
{
assertThrow(data() < (u256(1) << 64), util::Exception, "Tag already has subassembly set.");
assertThrow(m_type == PushTag || m_type == Tag, util::Exception, "");
size_t tag = size_t(u256(data()) & 0xffffffffffffffffULL);
auto tag = static_cast<size_t>(u256(data()) & 0xffffffffffffffffULL);
AssemblyItem r = *this;
r.m_type = PushTag;
r.setPushTagSubIdAndTag(_subId, tag);
@ -45,8 +45,8 @@ pair<size_t, size_t> AssemblyItem::splitForeignPushTag() const
{
assertThrow(m_type == PushTag || m_type == Tag, util::Exception, "");
u256 combined = u256(data());
size_t subId = size_t((combined >> 64) - 1);
size_t tag = size_t(combined & 0xffffffffffffffffULL);
size_t subId = static_cast<size_t>((combined >> 64) - 1);
size_t tag = static_cast<size_t>(combined & 0xffffffffffffffffULL);
return make_pair(subId, tag);
}
@ -54,7 +54,7 @@ void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag)
{
assertThrow(m_type == PushTag || m_type == Tag, util::Exception, "");
u256 data = _tag;
if (_subId != size_t(-1))
if (_subId != numeric_limits<size_t>::max())
data |= (u256(_subId) + 1) << 64;
setData(data);
}
@ -93,22 +93,22 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const
assertThrow(false, InvalidOpcode, "");
}
int AssemblyItem::arguments() const
size_t AssemblyItem::arguments() const
{
if (type() == Operation)
return instructionInfo(instruction()).args;
return static_cast<size_t>(instructionInfo(instruction()).args);
else if (type() == AssignImmutable)
return 1;
else
return 0;
}
int AssemblyItem::returnValues() const
size_t AssemblyItem::returnValues() const
{
switch (m_type)
{
case Operation:
return instructionInfo(instruction()).ret;
return static_cast<size_t>(instructionInfo(instruction()).ret);
case Push:
case PushString:
case PushTag:
@ -193,7 +193,7 @@ string AssemblyItem::toAssemblyText() const
size_t sub{0};
size_t tag{0};
tie(sub, tag) = splitForeignPushTag();
if (sub == size_t(-1))
if (sub == numeric_limits<size_t>::max())
text = string("tag_") + to_string(tag);
else
text = string("tag_") + to_string(sub) + "_" + to_string(tag);
@ -201,16 +201,16 @@ string AssemblyItem::toAssemblyText() const
}
case Tag:
assertThrow(data() < 0x10000, AssemblyException, "Declaration of sub-assembly tag.");
text = string("tag_") + to_string(size_t(data())) + ":";
text = string("tag_") + to_string(static_cast<size_t>(data())) + ":";
break;
case PushData:
text = string("data_") + util::toHex(data());
break;
case PushSub:
text = string("dataOffset(sub_") + to_string(size_t(data())) + ")";
text = string("dataOffset(sub_") + to_string(static_cast<size_t>(data())) + ")";
break;
case PushSubSize:
text = string("dataSize(sub_") + to_string(size_t(data())) + ")";
text = string("dataSize(sub_") + to_string(static_cast<size_t>(data())) + ")";
break;
case PushProgramSize:
text = string("bytecodeSize");
@ -262,7 +262,7 @@ ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item)
case PushTag:
{
size_t subId = _item.splitForeignPushTag().first;
if (subId == size_t(-1))
if (subId == numeric_limits<size_t>::max())
_out << " PushTag " << _item.splitForeignPushTag().second;
else
_out << " PushTag " << subId << ":" << _item.splitForeignPushTag().second;
@ -272,13 +272,13 @@ ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item)
_out << " Tag " << _item.data();
break;
case PushData:
_out << " PushData " << hex << (unsigned)_item.data() << dec;
_out << " PushData " << hex << static_cast<unsigned>(_item.data()) << dec;
break;
case PushSub:
_out << " PushSub " << hex << size_t(_item.data()) << dec;
_out << " PushSub " << hex << static_cast<size_t>(_item.data()) << dec;
break;
case PushSubSize:
_out << " PushSubSize " << hex << size_t(_item.data()) << dec;
_out << " PushSubSize " << hex << static_cast<size_t>(_item.data()) << dec;
break;
case PushProgramSize:
_out << " PushProgramSize";
@ -317,7 +317,7 @@ std::string AssemblyItem::computeSourceMapping(
int prevStart = -1;
int prevLength = -1;
int prevSourceIndex = -1;
size_t prevModifierDepth = -1;
int prevModifierDepth = -1;
char prevJump = 0;
for (auto const& item: _items)
{
@ -328,14 +328,14 @@ std::string AssemblyItem::computeSourceMapping(
int length = location.start != -1 && location.end != -1 ? location.end - location.start : -1;
int sourceIndex =
location.source && _sourceIndicesMap.count(location.source->name()) ?
_sourceIndicesMap.at(location.source->name()) :
static_cast<int>(_sourceIndicesMap.at(location.source->name())) :
-1;
char jump = '-';
if (item.getJumpType() == evmasm::AssemblyItem::JumpType::IntoFunction)
jump = 'i';
else if (item.getJumpType() == evmasm::AssemblyItem::JumpType::OutOfFunction)
jump = 'o';
size_t modifierDepth = item.m_modifierDepth;
int modifierDepth = static_cast<int>(item.m_modifierDepth);
unsigned components = 5;
if (modifierDepth == prevModifierDepth)

View File

@ -134,9 +134,9 @@ public:
/// @returns an upper bound for the number of bytes required by this item, assuming that
/// the value of a jump tag takes @a _addressLength bytes.
unsigned bytesRequired(unsigned _addressLength) const;
int arguments() const;
int returnValues() const;
int deposit() const { return returnValues() - arguments(); }
size_t arguments() const;
size_t returnValues() const;
size_t deposit() const { return returnValues() - arguments(); }
/// @returns true if the assembly item can be used in a functional context.
bool canBeFunctional() const;

View File

@ -63,8 +63,9 @@ bool BlockDeduplicator::deduplicate()
if (_j < m_items.size() && m_items.at(_j).type() == Tag)
pushSecondTag = m_items.at(_j).pushTag();
BlockIterator first{m_items.begin() + _i, m_items.end(), &pushFirstTag, &pushSelf};
BlockIterator second{m_items.begin() + _j, m_items.end(), &pushSecondTag, &pushSelf};
using diff_type = BlockIterator::difference_type;
BlockIterator first{m_items.begin() + diff_type(_i), m_items.end(), &pushFirstTag, &pushSelf};
BlockIterator second{m_items.begin() + diff_type(_j), m_items.end(), &pushSecondTag, &pushSelf};
BlockIterator end{m_items.end(), m_items.end()};
if (first != end && (*first).type() == Tag)
@ -113,10 +114,14 @@ bool BlockDeduplicator::applyTagReplacement(
if (subId != _subId)
continue;
auto it = _replacements.find(tagId);
// Recursively look for the element replaced by tagId
for (auto _it = it; _it != _replacements.end(); _it = _replacements.find(_it->second))
it = _it;
if (it != _replacements.end())
{
changed = true;
item.setPushTagSubIdAndTag(subId, size_t(it->second));
item.setPushTagSubIdAndTag(subId, static_cast<size_t>(it->second));
}
}
return changed;

View File

@ -386,7 +386,11 @@ void CSECodeGenerator::generateClassElement(Id _c, bool _allowSequenced)
"Opcodes with more than two arguments not implemented yet."
);
for (size_t i = 0; i < arguments.size(); ++i)
assertThrow(m_stack[m_stackHeight - i] == arguments[i], OptimizerException, "Expected arguments not present." );
assertThrow(
m_stack[m_stackHeight - static_cast<int>(i)] == arguments[i],
OptimizerException,
"Expected arguments not present."
);
while (SemanticInformation::isCommutativeOperation(*expr.item) &&
!m_generatedItems.empty() &&
@ -395,8 +399,8 @@ void CSECodeGenerator::generateClassElement(Id _c, bool _allowSequenced)
appendOrRemoveSwap(m_stackHeight - 1, itemLocation);
for (size_t i = 0; i < arguments.size(); ++i)
{
m_classPositions[m_stack[m_stackHeight - i]].erase(m_stackHeight - i);
m_stack.erase(m_stackHeight - i);
m_classPositions[m_stack[m_stackHeight - static_cast<int>(i)]].erase(m_stackHeight - static_cast<int>(i));
m_stack.erase(m_stackHeight - static_cast<int>(i));
}
appendItem(*expr.item);
if (expr.item->type() != Operation || instructionInfo(expr.item->instruction()).ret == 1)
@ -467,7 +471,7 @@ void CSECodeGenerator::appendDup(int _fromPosition, SourceLocation const& _locat
int instructionNum = 1 + m_stackHeight - _fromPosition;
assertThrow(instructionNum <= 16, StackTooDeepException, "Stack too deep, try removing local variables.");
assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access.");
appendItem(AssemblyItem(dupInstruction(instructionNum), _location));
appendItem(AssemblyItem(dupInstruction(static_cast<unsigned>(instructionNum)), _location));
m_stack[m_stackHeight] = m_stack[_fromPosition];
m_classPositions[m_stack[m_stackHeight]].insert(m_stackHeight);
}
@ -480,7 +484,7 @@ void CSECodeGenerator::appendOrRemoveSwap(int _fromPosition, SourceLocation cons
int instructionNum = m_stackHeight - _fromPosition;
assertThrow(instructionNum <= 16, StackTooDeepException, "Stack too deep, try removing local variables.");
assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access.");
appendItem(AssemblyItem(swapInstruction(instructionNum), _location));
appendItem(AssemblyItem(swapInstruction(static_cast<unsigned>(instructionNum)), _location));
if (m_stack[m_stackHeight] != m_stack[_fromPosition])
{
@ -502,5 +506,5 @@ void CSECodeGenerator::appendOrRemoveSwap(int _fromPosition, SourceLocation cons
void CSECodeGenerator::appendItem(AssemblyItem const& _item)
{
m_generatedItems.push_back(_item);
m_stackHeight += _item.deposit();
m_stackHeight += static_cast<int>(_item.deposit());
}

View File

@ -262,7 +262,7 @@ bool ComputeMethod::checkRepresentation(u256 const& _value, AssemblyItems const&
{
case Operation:
{
if (stack.size() < size_t(item.arguments()))
if (stack.size() < item.arguments())
return false;
u256* sp = &stack.back();
switch (item.instruction())
@ -320,7 +320,7 @@ bool ComputeMethod::checkRepresentation(u256 const& _value, AssemblyItems const&
bigint ComputeMethod::gasNeeded(AssemblyItems const& _routine) const
{
size_t numExps = count(_routine.begin(), _routine.end(), Instruction::EXP);
auto numExps = static_cast<size_t>(count(_routine.begin(), _routine.end(), Instruction::EXP));
return combineGas(
simpleRunGas(_routine) + numExps * (GasCosts::expGas + GasCosts::expByteGas(m_params.evmVersion)),
// Data gas for routine: Some bytes are zero, but we ignore them.

View File

@ -45,8 +45,8 @@ public:
BlockId() { *this = invalid(); }
explicit BlockId(unsigned _id): m_id(_id) {}
explicit BlockId(u256 const& _id);
static BlockId initial() { return BlockId(-2); }
static BlockId invalid() { return BlockId(-1); }
static BlockId initial() { return BlockId(std::numeric_limits<unsigned>::max() - 1); }
static BlockId invalid() { return BlockId(std::numeric_limits<unsigned>::max()); }
bool operator==(BlockId const& _other) const { return m_id == _other.m_id; }
bool operator!=(BlockId const& _other) const { return m_id != _other.m_id; }

View File

@ -190,7 +190,7 @@ ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr)
_expr.item->type() != Operation ||
!SemanticInformation::isDeterministic(*_expr.item)
)
return -1;
return numeric_limits<unsigned>::max();
if (auto match = rules.findFirstMatch(_expr, *this))
{
@ -208,7 +208,7 @@ ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr)
return rebuildExpression(ExpressionTemplate(match->action(), _expr.item->location()));
}
return -1;
return numeric_limits<unsigned>::max();
}
ExpressionClasses::Id ExpressionClasses::rebuildExpression(ExpressionTemplate const& _template)

View File

@ -330,8 +330,8 @@ void solidity::evmasm::eachInstruction(
{
for (auto it = _mem.begin(); it < _mem.end(); ++it)
{
Instruction instr = Instruction(*it);
size_t additional = 0;
auto instr = Instruction(*it);
int additional = 0;
if (isValidInstruction(instr))
additional = instructionInfo(instr).additional;
@ -357,7 +357,7 @@ string solidity::evmasm::disassemble(bytes const& _mem)
stringstream ret;
eachInstruction(_mem, [&](Instruction _instr, u256 const& _data) {
if (!isValidInstruction(_instr))
ret << "0x" << std::uppercase << std::hex << int(_instr) << " ";
ret << "0x" << std::uppercase << std::hex << static_cast<int>(_instr) << " ";
else
{
InstructionInfo info = instructionInfo(_instr);

View File

@ -30,7 +30,7 @@ using namespace solidity::evmasm;
bool JumpdestRemover::optimise(set<size_t> const& _tagsReferencedFromOutside)
{
set<size_t> references{referencedTags(m_items, -1)};
set<size_t> references{referencedTags(m_items, numeric_limits<size_t>::max())};
references.insert(_tagsReferencedFromOutside.begin(), _tagsReferencedFromOutside.end());
size_t initialSize = m_items.size();
@ -43,7 +43,7 @@ bool JumpdestRemover::optimise(set<size_t> const& _tagsReferencedFromOutside)
if (_item.type() != Tag)
return false;
auto asmIdAndTag = _item.splitForeignPushTag();
assertThrow(asmIdAndTag.first == size_t(-1), OptimizerException, "Sub-assembly tag used as label.");
assertThrow(asmIdAndTag.first == numeric_limits<size_t>::max(), OptimizerException, "Sub-assembly tag used as label.");
size_t tag = asmIdAndTag.second;
return !references.count(tag);
}

View File

@ -41,7 +41,7 @@ ostream& KnownState::stream(ostream& _out) const
if (!expr.item)
_out << " no item";
else if (expr.item->type() == UndefinedItem)
_out << " unknown " << int(expr.item->data());
_out << " unknown " << static_cast<int>(expr.item->data());
else
_out << *expr.item;
if (expr.sequenceNumber)
@ -112,21 +112,21 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
setStackElement(
m_stackHeight + 1,
stackElement(
m_stackHeight - int(instruction) + int(Instruction::DUP1),
m_stackHeight - static_cast<int>(instruction) + static_cast<int>(Instruction::DUP1),
_item.location()
)
);
else if (SemanticInformation::isSwapInstruction(_item))
swapStackElements(
m_stackHeight,
m_stackHeight - 1 - int(instruction) + int(Instruction::SWAP1),
m_stackHeight - 1 - static_cast<int>(instruction) + static_cast<int>(Instruction::SWAP1),
_item.location()
);
else if (instruction != Instruction::POP)
{
vector<Id> arguments(info.args);
for (int i = 0; i < info.args; ++i)
arguments[i] = stackElement(m_stackHeight - i, _item.location());
vector<Id> arguments(static_cast<size_t>(info.args));
for (size_t i = 0; i < static_cast<size_t>(info.args); ++i)
arguments[i] = stackElement(m_stackHeight - static_cast<int>(i), _item.location());
switch (_item.instruction())
{
case Instruction::SSTORE:
@ -134,7 +134,7 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
break;
case Instruction::SLOAD:
setStackElement(
m_stackHeight + _item.deposit(),
m_stackHeight + static_cast<int>(_item.deposit()),
loadFromStorage(arguments[0], _item.location())
);
break;
@ -143,13 +143,13 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
break;
case Instruction::MLOAD:
setStackElement(
m_stackHeight + _item.deposit(),
m_stackHeight + static_cast<int>(_item.deposit()),
loadFromMemory(arguments[0], _item.location())
);
break;
case Instruction::KECCAK256:
setStackElement(
m_stackHeight + _item.deposit(),
m_stackHeight + static_cast<int>(_item.deposit()),
applyKeccak256(arguments.at(0), arguments.at(1), _item.location())
);
break;
@ -167,16 +167,16 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
assertThrow(info.ret <= 1, InvalidDeposit, "");
if (info.ret == 1)
setStackElement(
m_stackHeight + _item.deposit(),
m_stackHeight + static_cast<int>(_item.deposit()),
m_expressionClasses->find(_item, arguments, _copyItem)
);
}
}
m_stackElements.erase(
m_stackElements.upper_bound(m_stackHeight + _item.deposit()),
m_stackElements.upper_bound(m_stackHeight + static_cast<int>(_item.deposit())),
m_stackElements.end()
);
m_stackHeight += _item.deposit();
m_stackHeight += static_cast<int>(_item.deposit());
}
return op;
}
@ -388,7 +388,7 @@ KnownState::Id KnownState::applyKeccak256(
bytes data;
for (Id a: arguments)
data += util::toBigEndian(*m_expressionClasses->knownConstant(a));
data.resize(size_t(*l));
data.resize(static_cast<size_t>(*l));
v = m_expressionClasses->find(AssemblyItem(u256(util::keccak256(data)), _location));
}
else

View File

@ -40,7 +40,7 @@ void LinkerObject::link(map<string, h160> const& _libraryAddresses)
std::map<size_t, std::string> remainingRefs;
for (auto const& linkRef: linkReferences)
if (h160 const* address = matchLibrary(linkRef.second, _libraryAddresses))
copy(address->data(), address->data() + 20, bytecode.begin() + linkRef.first);
copy(address->data(), address->data() + 20, bytecode.begin() + vector<uint8_t>::difference_type(linkRef.first));
else
remainingRefs.insert(linkRef);
linkReferences.swap(remainingRefs);

View File

@ -84,7 +84,7 @@ struct SimplePeepholeOptimizerMethod
{
if (
_state.i + WindowSize <= _state.items.size() &&
ApplyRule<Method, WindowSize>::applyRule(_state.items.begin() + _state.i, _state.out)
ApplyRule<Method, WindowSize>::applyRule(_state.items.begin() + static_cast<ptrdiff_t>(_state.i), _state.out)
)
{
_state.i += WindowSize;
@ -303,7 +303,7 @@ struct UnreachableCode
{
static bool apply(OptimiserState& _state)
{
auto it = _state.items.begin() + _state.i;
auto it = _state.items.begin() + static_cast<ptrdiff_t>(_state.i);
auto end = _state.items.end();
if (it == end)
return false;
@ -317,13 +317,13 @@ struct UnreachableCode
)
return false;
size_t i = 1;
ptrdiff_t i = 1;
while (it + i != end && it[i].type() != Tag)
i++;
if (i > 1)
{
*_state.out = it[0];
_state.i += i;
_state.i += static_cast<size_t>(i);
return true;
}
else
@ -345,7 +345,7 @@ void applyMethods(OptimiserState& _state, Method, OtherMethods... _other)
size_t numberOfPops(AssemblyItems const& _items)
{
return std::count(_items.begin(), _items.end(), Instruction::POP);
return static_cast<size_t>(std::count(_items.begin(), _items.end(), Instruction::POP));
}
}

View File

@ -29,6 +29,10 @@
#include <boost/multiprecision/detail/min_max.hpp>
#include <libyul/Dialect.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <liblangutil/EVMVersion.h>
#include <vector>
#include <functional>
@ -657,12 +661,37 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart9(
return rules;
}
template<class Pattern>
std::vector<SimplificationRule<Pattern>> evmRuleList(
langutil::EVMVersion _evmVersion,
Pattern,
Pattern,
Pattern,
Pattern,
Pattern,
Pattern,
Pattern
)
{
using Builtins = typename Pattern::Builtins;
std::vector<SimplificationRule<Pattern>> rules;
if (_evmVersion.hasSelfBalance())
rules.push_back({
Builtins::BALANCE(Instruction::ADDRESS),
[]() -> Pattern { return Instruction::SELFBALANCE; }, false
});
return rules;
}
/// @returns a list of simplification rules given certain match placeholders.
/// A, B and C should represent constants, W, X, Y, and Z arbitrary expressions.
/// The simplifications should never change the order of evaluation of
/// arbitrary operations.
template <class Pattern>
std::vector<SimplificationRule<Pattern>> simplificationRuleList(
std::optional<langutil::EVMVersion> _evmVersion,
Pattern A,
Pattern B,
Pattern C,
@ -691,6 +720,10 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleList(
rules += simplificationRuleListPart7(A, B, C, W, X);
rules += simplificationRuleListPart8(A, B, C, W, X);
rules += simplificationRuleListPart9(A, B, C, W, X, Y, Z);
if (_evmVersion.has_value())
rules += evmRuleList(*_evmVersion, A, B, C, W, X, Y, Z);
return rules;
}

View File

@ -92,7 +92,7 @@ Rules::Rules()
Y.setMatchGroup(6, m_matchGroups);
Z.setMatchGroup(7, m_matchGroups);
addRules(simplificationRuleList(A, B, C, W, X, Y, Z));
addRules(simplificationRuleList(nullopt, A, B, C, W, X, Y, Z));
assertThrow(isInitialized(), OptimizerException, "Rule list not properly initialized.");
}

View File

@ -85,7 +85,7 @@ string CharStream::lineAtPosition(int _position) const
{
// if _position points to \n, it returns the line before the \n
using size_type = string::size_type;
size_type searchStart = min<size_type>(m_source.size(), _position);
size_type searchStart = min<size_type>(m_source.size(), size_type(_position));
if (searchStart > 0)
searchStart--;
size_type lineStart = m_source.rfind('\n', searchStart);
@ -105,8 +105,9 @@ string CharStream::lineAtPosition(int _position) const
tuple<int, int> CharStream::translatePositionToLineColumn(int _position) const
{
using size_type = string::size_type;
size_type searchPosition = min<size_type>(m_source.size(), _position);
int lineNumber = count(m_source.begin(), m_source.begin() + searchPosition, '\n');
using diff_type = string::difference_type;
size_type searchPosition = min<size_type>(m_source.size(), size_type(_position));
int lineNumber = count(m_source.begin(), m_source.begin() + diff_type(searchPosition), '\n');
size_type lineStart;
if (searchPosition == 0)
lineStart = 0;

View File

@ -72,7 +72,7 @@ public:
explicit CharStream(std::string _source, std::string name):
m_source(std::move(_source)), m_name(std::move(name)) {}
int position() const { return m_position; }
size_t position() const { return m_position; }
bool isPastEndOfInput(size_t _charsForward = 0) const { return (m_position + _charsForward) >= m_source.size(); }
char get(size_t _charsForward = 0) const { return m_source[m_position + _charsForward]; }

View File

@ -28,8 +28,6 @@ using namespace std;
using namespace solidity;
using namespace solidity::langutil;
ErrorId solidity::langutil::operator"" _error(unsigned long long _error) { return ErrorId{ _error }; }
ErrorReporter& ErrorReporter::operator=(ErrorReporter const& _errorReporter)
{
if (&_errorReporter == this)
@ -62,12 +60,12 @@ void ErrorReporter::warning(
error(_error, Error::Type::Warning, _location, _secondaryLocation, _description);
}
void ErrorReporter::error(ErrorId, Error::Type _type, SourceLocation const& _location, string const& _description)
void ErrorReporter::error(ErrorId _errorId, Error::Type _type, SourceLocation const& _location, string const& _description)
{
if (checkForExcessiveErrors(_type))
return;
auto err = make_shared<Error>(_type);
auto err = make_shared<Error>(_errorId, _type);
*err <<
errinfo_sourceLocation(_location) <<
util::errinfo_comment(_description);
@ -75,12 +73,12 @@ void ErrorReporter::error(ErrorId, Error::Type _type, SourceLocation const& _loc
m_errorList.push_back(err);
}
void ErrorReporter::error(ErrorId, Error::Type _type, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description)
void ErrorReporter::error(ErrorId _errorId, Error::Type _type, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description)
{
if (checkForExcessiveErrors(_type))
return;
auto err = make_shared<Error>(_type);
auto err = make_shared<Error>(_errorId, _type);
*err <<
errinfo_sourceLocation(_location) <<
errinfo_secondarySourceLocation(_secondaryLocation) <<
@ -102,7 +100,7 @@ bool ErrorReporter::checkForExcessiveErrors(Error::Type _type)
if (m_warningCount == c_maxWarningsAllowed)
{
auto err = make_shared<Error>(Error::Type::Warning);
auto err = make_shared<Error>(4591_error, Error::Type::Warning);
*err << util::errinfo_comment("There are more than 256 warnings. Ignoring the rest.");
m_errorList.push_back(err);
}
@ -116,7 +114,7 @@ bool ErrorReporter::checkForExcessiveErrors(Error::Type _type)
if (m_errorCount > c_maxErrorsAllowed)
{
auto err = make_shared<Error>(Error::Type::Warning);
auto err = make_shared<Error>(4013_error, Error::Type::Warning);
*err << util::errinfo_comment("There are more than 256 errors. Aborting.");
m_errorList.push_back(err);
BOOST_THROW_EXCEPTION(FatalError());

View File

@ -23,6 +23,7 @@
#pragma once
#include <libsolutil/CommonData.h>
#include <libsolutil/Exceptions.h>
#include <liblangutil/Exceptions.h>
#include <liblangutil/SourceLocation.h>
@ -33,17 +34,6 @@
namespace solidity::langutil
{
/**
* Unique identifiers are used to tag and track individual error cases.
* They are passed as the first parameter of error reporting functions.
* Suffix _error helps to find them in the sources.
* The struct ErrorId prevents incidental calls like typeError(3141) instead of typeError(3141_error).
* To create a new ID, one can add 0000_error and then run "python ./scripts/correct_error_ids.py"
* from the root of the repo.
*/
struct ErrorId { unsigned long long error = 0; };
ErrorId operator"" _error(unsigned long long error);
class ErrorReporter
{
public:
@ -143,6 +133,28 @@ public:
// @returns true if the maximum error count has been reached.
bool hasExcessiveErrors() const;
class ErrorWatcher
{
public:
ErrorWatcher(ErrorReporter const& _errorReporter):
m_errorReporter(_errorReporter),
m_initialErrorCount(_errorReporter.errorCount())
{}
bool ok() const
{
solAssert(m_initialErrorCount <= m_errorReporter.errorCount(), "Unexpected error count.");
return m_initialErrorCount == m_errorReporter.errorCount();
}
private:
ErrorReporter const& m_errorReporter;
unsigned const m_initialErrorCount;
};
ErrorWatcher errorWatcher() const
{
return ErrorWatcher(*this);
}
private:
void error(
ErrorId _error,

View File

@ -26,7 +26,8 @@ using namespace std;
using namespace solidity;
using namespace solidity::langutil;
Error::Error(Type _type, SourceLocation const& _location, string const& _description):
Error::Error(ErrorId _errorId, Type _type, SourceLocation const& _location, string const& _description):
m_errorId(_errorId),
m_type(_type)
{
switch (m_type)
@ -57,8 +58,8 @@ Error::Error(Type _type, SourceLocation const& _location, string const& _descrip
*this << util::errinfo_comment(_description);
}
Error::Error(Error::Type _type, std::string const& _description, SourceLocation const& _location):
Error(_type)
Error::Error(ErrorId _errorId, Error::Type _type, std::string const& _description, SourceLocation const& _location):
Error(_errorId, _type)
{
if (_location.isValid())
*this << errinfo_sourceLocation(_location);

View File

@ -56,6 +56,17 @@ struct InvalidAstError: virtual util::Exception {};
#define astAssert(CONDITION, DESCRIPTION) \
assertThrow(CONDITION, ::solidity::langutil::InvalidAstError, DESCRIPTION)
/**
* Unique identifiers are used to tag and track individual error cases.
* They are passed as the first parameter of error reporting functions.
* Suffix _error helps to find them in the sources.
* The struct ErrorId prevents incidental calls like typeError(3141) instead of typeError(3141_error).
* To create a new ID, one can add 0000_error and then run "python ./scripts/fix_error_ids.py"
* from the root of the repo.
*/
struct ErrorId { unsigned long long error = 0; };
constexpr ErrorId operator"" _error(unsigned long long _error) { return ErrorId{ _error }; }
class Error: virtual public util::Exception
{
public:
@ -69,14 +80,16 @@ public:
Warning
};
explicit Error(
Error(
ErrorId _errorId,
Type _type,
SourceLocation const& _location = SourceLocation(),
std::string const& _description = std::string()
);
Error(Type _type, std::string const& _description, SourceLocation const& _location = SourceLocation());
Error(ErrorId _errorId, Type _type, std::string const& _description, SourceLocation const& _location = SourceLocation());
ErrorId errorId() const { return m_errorId; }
Type type() const { return m_type; }
std::string const& typeName() const { return m_typeName; }
@ -100,6 +113,7 @@ public:
return true;
}
private:
ErrorId m_errorId;
Type m_type;
std::string m_typeName;
};

View File

@ -106,7 +106,7 @@ void ParserBase::expectTokenOrConsumeUntil(Token _value, string const& _currentN
if (m_scanner->currentToken() == Token::EOS)
{
// rollback to where the token started, and raise exception to be caught at a higher level.
m_scanner->setPosition(startPosition);
m_scanner->setPosition(static_cast<size_t>(startPosition));
m_inParserRecovery = true;
fatalParserError(1957_error, errorLoc, msg);
}

View File

@ -64,7 +64,7 @@ protected:
};
/// Location of the current token
SourceLocation currentLocation() const;
virtual SourceLocation currentLocation() const;
///@{
///@name Helper functions

View File

@ -171,7 +171,7 @@ void Scanner::supportPeriodInIdentifier(bool _value)
bool Scanner::scanHexByte(char& o_scannedByte)
{
char x = 0;
for (int i = 0; i < 2; i++)
for (size_t i = 0; i < 2; i++)
{
int d = hexValue(m_char);
if (d < 0)
@ -189,7 +189,7 @@ bool Scanner::scanHexByte(char& o_scannedByte)
std::optional<unsigned> Scanner::scanUnicode()
{
unsigned x = 0;
for (int i = 0; i < 4; i++)
for (size_t i = 0; i < 4; i++)
{
int d = hexValue(m_char);
if (d < 0)
@ -197,7 +197,7 @@ std::optional<unsigned> Scanner::scanUnicode()
rollback(i);
return {};
}
x = x * 16 + d;
x = x * 16 + static_cast<size_t>(d);
advance();
}
return x;
@ -207,17 +207,17 @@ std::optional<unsigned> Scanner::scanUnicode()
void Scanner::addUnicodeAsUTF8(unsigned codepoint)
{
if (codepoint <= 0x7f)
addLiteralChar(codepoint);
addLiteralChar(char(codepoint));
else if (codepoint <= 0x7ff)
{
addLiteralChar(0xc0 | (codepoint >> 6));
addLiteralChar(0x80 | (codepoint & 0x3f));
addLiteralChar(char(0xc0u | (codepoint >> 6u)));
addLiteralChar(char(0x80u | (codepoint & 0x3fu)));
}
else
{
addLiteralChar(0xe0 | (codepoint >> 12));
addLiteralChar(0x80 | ((codepoint >> 6) & 0x3f));
addLiteralChar(0x80 | (codepoint & 0x3f));
addLiteralChar(char(0xe0u | (codepoint >> 12u)));
addLiteralChar(char(0x80u | ((codepoint >> 6u) & 0x3fu)));
addLiteralChar(char(0x80u | (codepoint & 0x3fu)));
}
}
@ -225,10 +225,10 @@ void Scanner::rescan()
{
size_t rollbackTo = 0;
if (m_skippedComments[Current].literal.empty())
rollbackTo = m_tokens[Current].location.start;
rollbackTo = static_cast<size_t>(m_tokens[Current].location.start);
else
rollbackTo = m_skippedComments[Current].location.start;
m_char = m_source->rollback(size_t(m_source->position()) - rollbackTo);
rollbackTo = static_cast<size_t>(m_skippedComments[Current].location.start);
m_char = m_source->rollback(m_source->position() - rollbackTo);
next();
next();
next();
@ -260,17 +260,20 @@ Token Scanner::selectToken(char _next, Token _then, Token _else)
bool Scanner::skipWhitespace()
{
int const startPosition = sourcePos();
size_t const startPosition = sourcePos();
while (isWhiteSpace(m_char))
advance();
// Return whether or not we skipped any characters.
return sourcePos() != startPosition;
}
void Scanner::skipWhitespaceExceptUnicodeLinebreak()
bool Scanner::skipWhitespaceExceptUnicodeLinebreak()
{
size_t const startPosition = sourcePos();
while (isWhiteSpace(m_char) && !isUnicodeLinebreak())
advance();
// Return whether or not we skipped any characters.
return sourcePos() != startPosition;
}
Token Scanner::skipSingleLineComment()
@ -306,10 +309,10 @@ bool Scanner::tryScanEndOfLine()
return false;
}
int Scanner::scanSingleLineDocComment()
size_t Scanner::scanSingleLineDocComment()
{
LiteralScope literal(this, LITERAL_TYPE_COMMENT);
int endPosition = m_source->position();
size_t endPosition = m_source->position();
advance(); //consume the last '/' at ///
skipWhitespaceExceptUnicodeLinebreak();
@ -321,7 +324,7 @@ int Scanner::scanSingleLineDocComment()
{
// Check if next line is also a single-line comment.
// If any whitespaces were skipped, use source position before.
if (!skipWhitespace())
if (!skipWhitespaceExceptUnicodeLinebreak())
endPosition = m_source->position();
if (!m_source->isPastEndOfInput(3) &&
@ -329,8 +332,10 @@ int Scanner::scanSingleLineDocComment()
m_source->get(1) == '/' &&
m_source->get(2) == '/')
{
addCommentLiteralChar('\n');
m_char = m_source->advanceAndGet(3);
if (atEndOfLine())
continue;
addCommentLiteralChar('\n');
}
else
break; // next line is not a documentation comment, we are done
@ -389,9 +394,11 @@ Token Scanner::scanMultiLineDocComment()
}
else if (!m_source->isPastEndOfInput(1) && m_source->get(0) == '*' && m_source->get(1) != '/')
{ // skip first '*' in subsequent lines
m_char = m_source->advanceAndGet(1);
if (atEndOfLine()) // ignores empty lines
continue;
if (charsAdded)
addCommentLiteralChar('\n');
m_char = m_source->advanceAndGet(2);
addCommentLiteralChar('\n'); // corresponds to the end of previous line
}
else if (!m_source->isPastEndOfInput(1) && m_source->get(0) == '*' && m_source->get(1) == '/')
{ // if after newline the comment ends, don't insert the newline
@ -422,7 +429,7 @@ Token Scanner::scanMultiLineDocComment()
Token Scanner::scanSlash()
{
int firstSlashPosition = sourcePos();
int firstSlashPosition = static_cast<int>(sourcePos());
advance();
if (m_char == '/')
{
@ -434,7 +441,7 @@ Token Scanner::scanSlash()
m_skippedComments[NextNext].location.start = firstSlashPosition;
m_skippedComments[NextNext].location.source = m_source;
m_skippedComments[NextNext].token = Token::CommentLiteral;
m_skippedComments[NextNext].location.end = scanSingleLineDocComment();
m_skippedComments[NextNext].location.end = static_cast<int>(scanSingleLineDocComment());
return Token::Whitespace;
}
else
@ -460,7 +467,7 @@ Token Scanner::scanSlash()
m_skippedComments[NextNext].location.start = firstSlashPosition;
m_skippedComments[NextNext].location.source = m_source;
comment = scanMultiLineDocComment();
m_skippedComments[NextNext].location.end = sourcePos();
m_skippedComments[NextNext].location.end = static_cast<int>(sourcePos());
m_skippedComments[NextNext].token = comment;
if (comment == Token::Illegal)
return Token::Illegal; // error already set
@ -488,7 +495,7 @@ void Scanner::scanToken()
do
{
// Remember the position of the next token
m_tokens[NextNext].location.start = sourcePos();
m_tokens[NextNext].location.start = static_cast<int>(sourcePos());
switch (m_char)
{
case '"':
@ -683,7 +690,7 @@ void Scanner::scanToken()
// whitespace.
}
while (token == Token::Whitespace);
m_tokens[NextNext].location.end = sourcePos();
m_tokens[NextNext].location.end = static_cast<int>(sourcePos());
m_tokens[NextNext].location.source = m_source;
m_tokens[NextNext].token = token;
m_tokens[NextNext].extendedTokenInfo = make_tuple(m, n);

View File

@ -196,7 +196,7 @@ private:
///@}
bool advance() { m_char = m_source->advanceAndGet(); return !m_source->isPastEndOfInput(); }
void rollback(int _amount) { m_char = m_source->rollback(_amount); }
void rollback(size_t _amount) { m_char = m_source->rollback(_amount); }
/// Rolls back to the start of the current token and re-runs the scanner.
void rescan();
@ -214,7 +214,7 @@ private:
/// Skips all whitespace and @returns true if something was skipped.
bool skipWhitespace();
/// Skips all whitespace that are neither '\r' nor '\n'.
void skipWhitespaceExceptUnicodeLinebreak();
bool skipWhitespaceExceptUnicodeLinebreak();
Token skipSingleLineComment();
Token skipMultiLineComment();
@ -231,7 +231,7 @@ private:
Token scanString();
Token scanHexString();
/// Scans a single line comment and returns its corrected end position.
int scanSingleLineDocComment();
size_t scanSingleLineDocComment();
Token scanMultiLineDocComment();
/// Scans a slash '/' and depending on the characters returns the appropriate token
Token scanSlash();
@ -245,7 +245,7 @@ private:
bool isUnicodeLinebreak();
/// Return the current source position.
int sourcePos() const { return m_source->position(); }
size_t sourcePos() const { return m_source->position(); }
bool isSourcePastEndOfInput() const { return m_source->isPastEndOfInput(); }
bool m_supportPeriodInIdentifier = false;

View File

@ -37,7 +37,7 @@ SemVerVersion::SemVerVersion(string const& _versionString)
{
unsigned v = 0;
for (; i != end && '0' <= *i && *i <= '9'; ++i)
v = v * 10 + (*i - '0');
v = v * 10 + unsigned(*i - '0');
numbers[level] = v;
if (level < 2)
{
@ -100,10 +100,10 @@ bool SemVerMatchExpression::MatchComponent::matches(SemVerVersion const& _versio
int cmp = 0;
bool didCompare = false;
for (unsigned i = 0; i < levelsPresent && cmp == 0; i++)
if (version.numbers[i] != unsigned(-1))
if (version.numbers[i] != std::numeric_limits<unsigned>::max())
{
didCompare = true;
cmp = _version.numbers[i] - version.numbers[i];
cmp = static_cast<int>(_version.numbers[i] - version.numbers[i]);
}
if (cmp == 0 && !_version.prerelease.empty() && didCompare)
@ -245,14 +245,14 @@ unsigned SemVerMatchExpressionParser::parseVersionPart()
return 0;
else if ('1' <= c && c <= '9')
{
unsigned v = c - '0';
unsigned v(c - '0');
// If we skip to the next token, the current number is terminated.
while (m_pos == startPos && '0' <= currentChar() && currentChar() <= '9')
{
c = currentChar();
if (v * 10 < v || v * 10 + (c - '0') < v * 10)
if (v * 10 < v || v * 10 + unsigned(c - '0') < v * 10)
throw SemVerError();
v = v * 10 + c - '0';
v = v * 10 + unsigned(c - '0');
nextChar();
}
return v;

View File

@ -85,7 +85,7 @@ struct SourceLocation
assertThrow(0 <= start, SourceLocationError, "Invalid source location.");
assertThrow(start <= end, SourceLocationError, "Invalid source location.");
assertThrow(end <= int(source->source().length()), SourceLocationError, "Invalid source location.");
return source->source().substr(start, end - start);
return source->source().substr(size_t(start), size_t(end - start));
}
/// @returns the smallest SourceLocation that contains both @param _a and @param _b.
@ -113,7 +113,11 @@ struct SourceLocation
std::shared_ptr<CharStream> source;
};
SourceLocation const parseSourceLocation(std::string const& _input, std::string const& _sourceName, size_t _maxIndex = -1);
SourceLocation const parseSourceLocation(
std::string const& _input,
std::string const& _sourceName,
size_t _maxIndex = std::numeric_limits<size_t>::max()
);
/// Stream output for Location (used e.g. in boost exceptions).
inline std::ostream& operator<<(std::ostream& _out, SourceLocation const& _location)

View File

@ -58,11 +58,15 @@ SourceReference SourceReferenceExtractor::extract(SourceLocation const* _locatio
string line = source->lineAtPosition(_location->start);
int locationLength = isMultiline ? line.length() - start.column : end.column - start.column;
int locationLength =
isMultiline ?
int(line.length()) - start.column :
end.column - start.column;
if (locationLength > 150)
{
int const lhs = start.column + 35;
int const rhs = (isMultiline ? line.length() : end.column) - 35;
auto const lhs = static_cast<size_t>(start.column) + 35;
string::size_type const rhs = (isMultiline ? line.length() : static_cast<size_t>(end.column)) - 35;
line = line.substr(0, lhs) + " ... " + line.substr(rhs);
end.column = start.column + 75;
locationLength = 75;
@ -70,8 +74,13 @@ SourceReference SourceReferenceExtractor::extract(SourceLocation const* _locatio
if (line.length() > 150)
{
int const len = line.length();
line = line.substr(max(0, start.column - 35), min(start.column, 35) + min(locationLength + 35, len - start.column));
int const len = static_cast<int>(line.length());
line = line.substr(
static_cast<size_t>(max(0, start.column - 35)),
static_cast<size_t>(min(start.column, 35)) + static_cast<size_t>(
min(locationLength + 35,len - start.column)
)
);
if (start.column + locationLength + 35 < len)
line += " ...";
if (start.column > 35)
@ -79,7 +88,7 @@ SourceReference SourceReferenceExtractor::extract(SourceLocation const* _locatio
line = " ... " + line;
start.column = 40;
}
end.column = start.column + locationLength;
end.column = start.column + static_cast<int>(locationLength);
}
return SourceReference{

View File

@ -51,7 +51,7 @@ void SourceReferenceFormatter::printSourceLocation(SourceReference const& _ref)
);
m_stream << "^";
if (_ref.endColumn > _ref.startColumn + 2)
m_stream << string(_ref.endColumn - _ref.startColumn - 2, '-');
m_stream << string(static_cast<size_t>(_ref.endColumn - _ref.startColumn - 2), '-');
if (_ref.endColumn > _ref.startColumn + 1)
m_stream << "^";
m_stream << endl;
@ -60,7 +60,7 @@ void SourceReferenceFormatter::printSourceLocation(SourceReference const& _ref)
m_stream <<
_ref.text <<
endl <<
string(_ref.startColumn, ' ') <<
string(static_cast<size_t>(_ref.startColumn), ' ') <<
"^ (Relevant source part starts here and spans across multiple lines)." <<
endl;
}

View File

@ -21,7 +21,9 @@
#include <liblangutil/SourceReferenceFormatterHuman.h>
#include <liblangutil/Scanner.h>
#include <liblangutil/Exceptions.h>
#include <libsolutil/UTF8.h>
#include <iomanip>
#include <string_view>
using namespace std;
using namespace solidity;
@ -29,6 +31,20 @@ using namespace solidity::langutil;
using namespace solidity::util;
using namespace solidity::util::formatting;
namespace
{
std::string replaceNonTabs(std::string_view _utf8Input, char _filler)
{
std::string output;
for (char const c: _utf8Input)
if ((c & 0xc0) != 0x80)
output.push_back(c == '\t' ? '\t' : _filler);
return output;
}
}
AnsiColorized SourceReferenceFormatterHuman::normalColored() const
{
return AnsiColorized(m_stream, m_colored, {WHITE});
@ -84,9 +100,11 @@ void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _
frameColored() << "-->";
m_stream << ' ' << _ref.sourceName << ':' << line << ':' << (_ref.position.column + 1) << ":\n";
string_view text = _ref.text;
if (!_ref.multiline)
{
int const locationLength = _ref.endColumn - _ref.startColumn;
auto const locationLength = static_cast<size_t>(_ref.endColumn - _ref.startColumn);
// line 1:
m_stream << leftpad << ' ';
@ -95,20 +113,17 @@ void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _
// line 2:
frameColored() << line << " |";
m_stream << ' ' << _ref.text.substr(0, _ref.startColumn);
highlightColored() << _ref.text.substr(_ref.startColumn, locationLength);
m_stream << _ref.text.substr(_ref.endColumn) << '\n';
m_stream << ' ' << text.substr(0, static_cast<size_t>(_ref.startColumn));
highlightColored() << text.substr(static_cast<size_t>(_ref.startColumn), locationLength);
m_stream << text.substr(static_cast<size_t>(_ref.endColumn)) << '\n';
// line 3:
m_stream << leftpad << ' ';
frameColored() << '|';
m_stream << ' ';
for_each(
_ref.text.cbegin(),
_ref.text.cbegin() + _ref.startColumn,
[this](char ch) { m_stream << (ch == '\t' ? '\t' : ' '); }
);
diagColored() << string(locationLength, '^');
m_stream << ' ' << replaceNonTabs(text.substr(0, static_cast<size_t>(_ref.startColumn)), ' ');
diagColored() << replaceNonTabs(text.substr(static_cast<size_t>(_ref.startColumn), locationLength), '^');
m_stream << '\n';
}
else
@ -120,13 +135,13 @@ void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _
// line 2:
frameColored() << line << " |";
m_stream << ' ' << _ref.text.substr(0, _ref.startColumn);
highlightColored() << _ref.text.substr(_ref.startColumn) << '\n';
m_stream << ' ' << text.substr(0, static_cast<size_t>(_ref.startColumn));
highlightColored() << text.substr(static_cast<size_t>(_ref.startColumn)) << '\n';
// line 3:
m_stream << leftpad << ' ';
frameColored() << '|';
m_stream << ' ' << string(_ref.startColumn, ' ');
m_stream << ' ' << replaceNonTabs(text.substr(0, static_cast<size_t>(_ref.startColumn)), ' ');
diagColored() << "^ (Relevant source part starts here and spans across multiple lines).";
m_stream << '\n';
}

View File

@ -130,7 +130,7 @@ int parseSize(string::const_iterator _begin, string::const_iterator _end)
{
try
{
unsigned int m = boost::lexical_cast<int>(boost::make_iterator_range(_begin, _end));
int m = boost::lexical_cast<int>(boost::make_iterator_range(_begin, _end));
return m;
}
catch(boost::bad_lexical_cast const&)

View File

@ -15,7 +15,7 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libsolidity/formal/CHCSmtLib2Interface.h>
#include <libsmtutil/CHCSmtLib2Interface.h>
#include <libsolutil/Keccak256.h>
@ -32,7 +32,7 @@ using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
using namespace solidity::frontend::smt;
using namespace solidity::smtutil;
CHCSmtLib2Interface::CHCSmtLib2Interface(
map<h256, string> const& _queryResponses,
@ -51,10 +51,10 @@ void CHCSmtLib2Interface::reset()
m_variables.clear();
}
void CHCSmtLib2Interface::registerRelation(smt::Expression const& _expr)
void CHCSmtLib2Interface::registerRelation(Expression const& _expr)
{
solAssert(_expr.sort, "");
solAssert(_expr.sort->kind == smt::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);
@ -71,7 +71,7 @@ void CHCSmtLib2Interface::registerRelation(smt::Expression const& _expr)
}
}
void CHCSmtLib2Interface::addRule(smt::Expression const& _expr, std::string const& _name)
void CHCSmtLib2Interface::addRule(Expression const& _expr, std::string const& _name)
{
write(
"(rule (! " +
@ -82,7 +82,7 @@ void CHCSmtLib2Interface::addRule(smt::Expression const& _expr, std::string cons
);
}
pair<CheckResult, vector<string>> CHCSmtLib2Interface::query(smt::Expression const& _block)
pair<CheckResult, vector<string>> CHCSmtLib2Interface::query(Expression const& _block)
{
string accumulated{};
swap(m_accumulatedOutput, accumulated);
@ -112,7 +112,7 @@ pair<CheckResult, vector<string>> CHCSmtLib2Interface::query(smt::Expression con
void CHCSmtLib2Interface::declareVariable(string const& _name, SortPointer const& _sort)
{
solAssert(_sort, "");
smtAssert(_sort, "");
if (_sort->kind == Kind::Function)
declareFunction(_name, _sort);
else if (!m_variables.count(_name))
@ -124,13 +124,13 @@ void CHCSmtLib2Interface::declareVariable(string const& _name, SortPointer const
void CHCSmtLib2Interface::declareFunction(string const& _name, SortPointer const& _sort)
{
solAssert(_sort, "");
solAssert(_sort->kind == smt::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);
solAssert(fSort->codomain, "");
smtAssert(fSort->codomain, "");
string domain = m_smtlib2->toSmtLibSort(fSort->domain);
string codomain = m_smtlib2->toSmtLibSort(*fSort->codomain);
m_variables.insert(_name);

View File

@ -21,11 +21,11 @@
#pragma once
#include <libsolidity/formal/CHCSolverInterface.h>
#include <libsmtutil/CHCSolverInterface.h>
#include <libsolidity/formal/SMTLib2Interface.h>
#include <libsmtutil/SMTLib2Interface.h>
namespace solidity::frontend::smt
namespace solidity::smtutil
{
class CHCSmtLib2Interface: public CHCSolverInterface
@ -33,7 +33,7 @@ class CHCSmtLib2Interface: public CHCSolverInterface
public:
explicit CHCSmtLib2Interface(
std::map<util::h256, std::string> const& _queryResponses,
ReadCallback::Callback const& _smtCallback
frontend::ReadCallback::Callback const& _smtCallback
);
void reset();
@ -67,7 +67,7 @@ private:
std::map<util::h256, std::string> const& m_queryResponses;
std::vector<std::string> m_unhandledQueries;
ReadCallback::Callback m_smtCallback;
frontend::ReadCallback::Callback m_smtCallback;
};
}

View File

@ -21,9 +21,9 @@
#pragma once
#include <libsolidity/formal/SolverInterface.h>
#include <libsmtutil/SolverInterface.h>
namespace solidity::frontend::smt
namespace solidity::smtutil
{
class CHCSolverInterface

35
libsmtutil/CMakeLists.txt Normal file
View File

@ -0,0 +1,35 @@
set(sources
CHCSmtLib2Interface.cpp
CHCSmtLib2Interface.h
Exceptions.h
SMTLib2Interface.cpp
SMTLib2Interface.h
SMTPortfolio.cpp
SMTPortfolio.h
SolverInterface.h
Sorts.cpp
Sorts.h
)
if (${Z3_FOUND})
set(z3_SRCS Z3Interface.cpp Z3Interface.h Z3CHCInterface.cpp Z3CHCInterface.h)
else()
set(z3_SRCS)
endif()
if (${CVC4_FOUND})
set(cvc4_SRCS CVC4Interface.cpp CVC4Interface.h)
else()
set(cvc4_SRCS)
endif()
add_library(smtutil ${sources} ${z3_SRCS} ${cvc4_SRCS})
target_link_libraries(smtutil PUBLIC solutil Boost::boost)
if (${Z3_FOUND})
target_link_libraries(smtutil PUBLIC z3::libz3)
endif()
if (${CVC4_FOUND})
target_link_libraries(smtutil PUBLIC CVC4::CVC4)
endif()

View File

@ -15,15 +15,16 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libsolidity/formal/CVC4Interface.h>
#include <libsmtutil/CVC4Interface.h>
#include <liblangutil/Exceptions.h>
#include <libsolutil/CommonIO.h>
#include <cvc4/util/bitvector.h>
using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend::smt;
using namespace solidity::smtutil;
CVC4Interface::CVC4Interface():
m_solver(&m_context)
@ -51,7 +52,7 @@ void CVC4Interface::pop()
void CVC4Interface::declareVariable(string const& _name, SortPointer const& _sort)
{
solAssert(_sort, "");
smtAssert(_sort, "");
m_variables[_name] = m_context.mkVar(_name.c_str(), cvc4Sort(*_sort));
}
@ -63,19 +64,19 @@ void CVC4Interface::addAssertion(Expression const& _expr)
}
catch (CVC4::TypeCheckingException const& _e)
{
solAssert(false, _e.what());
smtAssert(false, _e.what());
}
catch (CVC4::LogicException const& _e)
{
solAssert(false, _e.what());
smtAssert(false, _e.what());
}
catch (CVC4::UnsafeInterruptException const& _e)
{
solAssert(false, _e.what());
smtAssert(false, _e.what());
}
catch (CVC4::Exception const& _e)
{
solAssert(false, _e.what());
smtAssert(false, _e.what());
}
}
@ -97,7 +98,7 @@ pair<CheckResult, vector<string>> CVC4Interface::check(vector<Expression> const&
result = CheckResult::UNKNOWN;
break;
default:
solAssert(false, "");
smtAssert(false, "");
}
if (result == CheckResult::SATISFIABLE && !_expressionsToEvaluate.empty())
@ -147,15 +148,15 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr)
}
catch (CVC4::TypeCheckingException const& _e)
{
solAssert(false, _e.what());
smtAssert(false, _e.what());
}
catch (CVC4::Exception const& _e)
{
solAssert(false, _e.what());
smtAssert(false, _e.what());
}
}
solAssert(_expr.hasCorrectArity(), "");
smtAssert(_expr.hasCorrectArity(), "");
if (n == "ite")
return arguments[0].iteExpr(arguments[1], arguments[2]);
else if (n == "not")
@ -186,6 +187,49 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr)
return m_context.mkExpr(CVC4::kind::INTS_DIVISION_TOTAL, arguments[0], arguments[1]);
else if (n == "mod")
return m_context.mkExpr(CVC4::kind::INTS_MODULUS, arguments[0], arguments[1]);
else if (n == "bvand")
return m_context.mkExpr(CVC4::kind::BITVECTOR_AND, arguments[0], arguments[1]);
else if (n == "int2bv")
{
size_t size = std::stoi(_expr.arguments[1].name);
auto i2bvOp = m_context.mkConst(CVC4::IntToBitVector(size));
// CVC4 treats all BVs as unsigned, so we need to manually apply 2's complement if needed.
return m_context.mkExpr(
CVC4::kind::ITE,
m_context.mkExpr(CVC4::kind::GEQ, arguments[0], m_context.mkConst(CVC4::Rational(0))),
m_context.mkExpr(CVC4::kind::INT_TO_BITVECTOR, i2bvOp, arguments[0]),
m_context.mkExpr(
CVC4::kind::BITVECTOR_NEG,
m_context.mkExpr(CVC4::kind::INT_TO_BITVECTOR, i2bvOp, m_context.mkExpr(CVC4::kind::UMINUS, arguments[0]))
)
);
}
else if (n == "bv2int")
{
auto intSort = dynamic_pointer_cast<IntSort>(_expr.sort);
smtAssert(intSort, "");
auto nat = m_context.mkExpr(CVC4::kind::BITVECTOR_TO_NAT, arguments[0]);
if (!intSort->isSigned)
return nat;
auto type = arguments[0].getType();
smtAssert(type.isBitVector(), "");
auto size = CVC4::BitVectorType(type).getSize();
// CVC4 treats all BVs as unsigned, so we need to manually apply 2's complement if needed.
auto extractOp = m_context.mkConst(CVC4::BitVectorExtract(size - 1, size - 1));
return m_context.mkExpr(CVC4::kind::ITE,
m_context.mkExpr(
CVC4::kind::EQUAL,
m_context.mkExpr(CVC4::kind::BITVECTOR_EXTRACT, extractOp, arguments[0]),
m_context.mkConst(CVC4::BitVector(1, size_t(0)))
),
nat,
m_context.mkExpr(
CVC4::kind::UMINUS,
m_context.mkExpr(CVC4::kind::BITVECTOR_TO_NAT, m_context.mkExpr(CVC4::kind::BITVECTOR_NEG, arguments[0]))
)
);
}
else if (n == "select")
return m_context.mkExpr(CVC4::kind::SELECT, arguments[0], arguments[1]);
else if (n == "store")
@ -193,32 +237,41 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr)
else if (n == "const_array")
{
shared_ptr<SortSort> sortSort = std::dynamic_pointer_cast<SortSort>(_expr.arguments[0].sort);
solAssert(sortSort, "");
smtAssert(sortSort, "");
return m_context.mkConst(CVC4::ArrayStoreAll(cvc4Sort(*sortSort->inner), arguments[1]));
}
else if (n == "tuple_get")
{
shared_ptr<TupleSort> tupleSort = std::dynamic_pointer_cast<TupleSort>(_expr.arguments[0].sort);
solAssert(tupleSort, "");
smtAssert(tupleSort, "");
CVC4::DatatypeType tt = m_context.mkTupleType(cvc4Sort(tupleSort->components));
CVC4::Datatype const& dt = tt.getDatatype();
size_t index = std::stoi(_expr.arguments[1].name);
size_t index = std::stoul(_expr.arguments[1].name);
CVC4::Expr s = dt[0][index].getSelector();
return m_context.mkExpr(CVC4::kind::APPLY_SELECTOR, s, arguments[0]);
}
else if (n == "tuple_constructor")
{
shared_ptr<TupleSort> tupleSort = std::dynamic_pointer_cast<TupleSort>(_expr.sort);
smtAssert(tupleSort, "");
CVC4::DatatypeType tt = m_context.mkTupleType(cvc4Sort(tupleSort->components));
CVC4::Datatype const& dt = tt.getDatatype();
CVC4::Expr c = dt[0].getConstructor();
return m_context.mkExpr(CVC4::kind::APPLY_CONSTRUCTOR, c, arguments);
}
solAssert(false, "");
smtAssert(false, "");
}
catch (CVC4::TypeCheckingException const& _e)
{
solAssert(false, _e.what());
smtAssert(false, _e.what());
}
catch (CVC4::Exception const& _e)
{
solAssert(false, _e.what());
smtAssert(false, _e.what());
}
solAssert(false, "");
smtAssert(false, "");
}
CVC4::Type CVC4Interface::cvc4Sort(Sort const& _sort)
@ -247,7 +300,7 @@ CVC4::Type CVC4Interface::cvc4Sort(Sort const& _sort)
default:
break;
}
solAssert(false, "");
smtAssert(false, "");
// Cannot be reached.
return m_context.integerType();
}

View File

@ -17,7 +17,7 @@
#pragma once
#include <libsolidity/formal/SolverInterface.h>
#include <libsmtutil/SolverInterface.h>
#include <boost/noncopyable.hpp>
#if defined(__GLIBC__)
@ -33,7 +33,7 @@
#undef _GLIBCXX_PERMIT_BACKWARD_HASH
#endif
namespace solidity::frontend::smt
namespace solidity::smtutil
{
class CVC4Interface: public SolverInterface, public boost::noncopyable
@ -53,8 +53,8 @@ public:
private:
CVC4::Expr toCVC4Expr(Expression const& _expr);
CVC4::Type cvc4Sort(smt::Sort const& _sort);
std::vector<CVC4::Type> cvc4Sort(std::vector<smt::SortPointer> const& _sorts);
CVC4::Type cvc4Sort(Sort const& _sort);
std::vector<CVC4::Type> cvc4Sort(std::vector<SortPointer> const& _sorts);
CVC4::ExprManager m_context;
CVC4::SmtEngine m_solver;

View File

@ -15,15 +15,17 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <libsolidity/formal/Sorts.h>
#include <libsolutil/Assertions.h>
#include <libsolutil/Exceptions.h>
using namespace std;
namespace solidity::frontend::smt
namespace solidity::smtutil
{
shared_ptr<Sort> const SortProvider::boolSort{make_shared<Sort>(Kind::Bool)};
shared_ptr<Sort> const SortProvider::intSort{make_shared<Sort>(Kind::Int)};
struct SMTLogicError: virtual util::Exception {};
#define smtAssert(CONDITION, DESCRIPTION) \
assertThrow(CONDITION, SMTLogicError, DESCRIPTION)
}

View File

@ -15,7 +15,7 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libsolidity/formal/SMTLib2Interface.h>
#include <libsmtutil/SMTLib2Interface.h>
#include <libsolutil/Keccak256.h>
@ -34,7 +34,7 @@ using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
using namespace solidity::frontend::smt;
using namespace solidity::smtutil;
SMTLib2Interface::SMTLib2Interface(
map<h256, string> const& _queryResponses,
@ -63,13 +63,13 @@ void SMTLib2Interface::push()
void SMTLib2Interface::pop()
{
solAssert(!m_accumulatedOutput.empty(), "");
smtAssert(!m_accumulatedOutput.empty(), "");
m_accumulatedOutput.pop_back();
}
void SMTLib2Interface::declareVariable(string const& _name, SortPointer const& _sort)
{
solAssert(_sort, "");
smtAssert(_sort, "");
if (_sort->kind == Kind::Function)
declareFunction(_name, _sort);
else if (!m_variables.count(_name))
@ -81,8 +81,8 @@ void SMTLib2Interface::declareVariable(string const& _name, SortPointer const& _
void SMTLib2Interface::declareFunction(string const& _name, SortPointer const& _sort)
{
solAssert(_sort, "");
solAssert(_sort->kind == smt::Kind::Function, "");
smtAssert(_sort, "");
smtAssert(_sort->kind == Kind::Function, "");
// TODO Use domain and codomain as key as well
if (!m_variables.count(_name))
{
@ -102,12 +102,12 @@ void SMTLib2Interface::declareFunction(string const& _name, SortPointer const& _
}
}
void SMTLib2Interface::addAssertion(smt::Expression const& _expr)
void SMTLib2Interface::addAssertion(Expression const& _expr)
{
write("(assert " + toSExpr(_expr) + ")");
}
pair<CheckResult, vector<string>> SMTLib2Interface::check(vector<smt::Expression> const& _expressionsToEvaluate)
pair<CheckResult, vector<string>> SMTLib2Interface::check(vector<Expression> const& _expressionsToEvaluate)
{
string response = querySolver(
boost::algorithm::join(m_accumulatedOutput, "\n") +
@ -126,34 +126,75 @@ pair<CheckResult, vector<string>> SMTLib2Interface::check(vector<smt::Expression
result = CheckResult::ERROR;
vector<string> values;
if (result == CheckResult::SATISFIABLE && result != CheckResult::ERROR)
if (result == CheckResult::SATISFIABLE && !_expressionsToEvaluate.empty())
values = parseValues(find(response.cbegin(), response.cend(), '\n'), response.cend());
return make_pair(result, values);
}
string SMTLib2Interface::toSExpr(smt::Expression const& _expr)
string SMTLib2Interface::toSExpr(Expression const& _expr)
{
if (_expr.arguments.empty())
return _expr.name;
std::string sexpr = "(";
if (_expr.name == "const_array")
if (_expr.name == "int2bv")
{
solAssert(_expr.arguments.size() == 2, "");
size_t size = std::stoi(_expr.arguments[1].name);
auto arg = toSExpr(_expr.arguments.front());
auto int2bv = "(_ int2bv " + to_string(size) + ")";
// Some solvers treat all BVs as unsigned, so we need to manually apply 2's complement if needed.
sexpr += string("ite ") +
"(>= " + arg + " 0) " +
"(" + int2bv + " " + arg + ") " +
"(bvneg (" + int2bv + " (- " + arg + ")))";
}
else if (_expr.name == "bv2int")
{
auto intSort = dynamic_pointer_cast<IntSort>(_expr.sort);
smtAssert(intSort, "");
auto arg = toSExpr(_expr.arguments.front());
auto nat = "(bv2nat " + arg + ")";
if (!intSort->isSigned)
return nat;
auto bvSort = dynamic_pointer_cast<BitVectorSort>(_expr.arguments.front().sort);
smtAssert(bvSort, "");
auto size = to_string(bvSort->size);
auto pos = to_string(bvSort->size - 1);
// Some solvers treat all BVs as unsigned, so we need to manually apply 2's complement if needed.
sexpr += string("ite ") +
"(= ((_ extract " + pos + " " + pos + ")" + arg + ") #b0) " +
nat + " " +
"(- (bvneg " + arg + "))";
}
else if (_expr.name == "const_array")
{
smtAssert(_expr.arguments.size() == 2, "");
auto sortSort = std::dynamic_pointer_cast<SortSort>(_expr.arguments.at(0).sort);
solAssert(sortSort, "");
smtAssert(sortSort, "");
auto arraySort = dynamic_pointer_cast<ArraySort>(sortSort->inner);
solAssert(arraySort, "");
smtAssert(arraySort, "");
sexpr += "(as const " + toSmtLibSort(*arraySort) + ") ";
sexpr += toSExpr(_expr.arguments.at(1));
}
else if (_expr.name == "tuple_get")
{
solAssert(_expr.arguments.size() == 2, "");
smtAssert(_expr.arguments.size() == 2, "");
auto tupleSort = dynamic_pointer_cast<TupleSort>(_expr.arguments.at(0).sort);
unsigned index = std::stoi(_expr.arguments.at(1).name);
solAssert(index < tupleSort->members.size(), "");
sexpr += tupleSort->members.at(index) + " " + toSExpr(_expr.arguments.at(0));
size_t index = std::stoul(_expr.arguments.at(1).name);
smtAssert(index < tupleSort->members.size(), "");
sexpr += "|" + tupleSort->members.at(index) + "| " + toSExpr(_expr.arguments.at(0));
}
else if (_expr.name == "tuple_constructor")
{
auto tupleSort = dynamic_pointer_cast<TupleSort>(_expr.sort);
smtAssert(tupleSort, "");
sexpr += "|" + tupleSort->name + "|";
for (auto const& arg: _expr.arguments)
sexpr += " " + toSExpr(arg);
}
else
{
@ -176,27 +217,28 @@ string SMTLib2Interface::toSmtLibSort(Sort const& _sort)
case Kind::Array:
{
auto const& arraySort = dynamic_cast<ArraySort const&>(_sort);
solAssert(arraySort.domain && arraySort.range, "");
smtAssert(arraySort.domain && arraySort.range, "");
return "(Array " + toSmtLibSort(*arraySort.domain) + ' ' + toSmtLibSort(*arraySort.range) + ')';
}
case Kind::Tuple:
{
auto const& tupleSort = dynamic_cast<TupleSort const&>(_sort);
if (!m_userSorts.count(tupleSort.name))
string tupleName = "|" + tupleSort.name + "|";
if (!m_userSorts.count(tupleName))
{
m_userSorts.insert(tupleSort.name);
string decl("(declare-datatypes ((" + tupleSort.name + " 0)) (((" + tupleSort.name);
solAssert(tupleSort.members.size() == tupleSort.components.size(), "");
m_userSorts.insert(tupleName);
string decl("(declare-datatypes ((" + tupleName + " 0)) (((" + tupleName);
smtAssert(tupleSort.members.size() == tupleSort.components.size(), "");
for (unsigned i = 0; i < tupleSort.members.size(); ++i)
decl += " (" + tupleSort.members.at(i) + " " + toSmtLibSort(*tupleSort.components.at(i)) + ")";
decl += " (|" + tupleSort.members.at(i) + "| " + toSmtLibSort(*tupleSort.components.at(i)) + ")";
decl += "))))";
write(decl);
}
return tupleSort.name;
return tupleName;
}
default:
solAssert(false, "Invalid SMT sort");
smtAssert(false, "Invalid SMT sort");
}
}
@ -211,11 +253,11 @@ string SMTLib2Interface::toSmtLibSort(vector<SortPointer> const& _sorts)
void SMTLib2Interface::write(string _data)
{
solAssert(!m_accumulatedOutput.empty(), "");
smtAssert(!m_accumulatedOutput.empty(), "");
m_accumulatedOutput.back() += move(_data) + "\n";
}
string SMTLib2Interface::checkSatAndGetValuesCommand(vector<smt::Expression> const& _expressionsToEvaluate)
string SMTLib2Interface::checkSatAndGetValuesCommand(vector<Expression> const& _expressionsToEvaluate)
{
string command;
if (_expressionsToEvaluate.empty())
@ -226,7 +268,7 @@ string SMTLib2Interface::checkSatAndGetValuesCommand(vector<smt::Expression> con
for (size_t i = 0; i < _expressionsToEvaluate.size(); i++)
{
auto const& e = _expressionsToEvaluate.at(i);
solAssert(e.sort->kind == Kind::Int || e.sort->kind == Kind::Bool, "Invalid sort for expression to evaluate.");
smtAssert(e.sort->kind == Kind::Int || e.sort->kind == Kind::Bool, "Invalid sort for expression to evaluate.");
command += "(declare-const |EVALEXPR_" + to_string(i) + "| " + (e.sort->kind == Kind::Int ? "Int" : "Bool") + ")\n";
command += "(assert (= |EVALEXPR_" + to_string(i) + "| " + toSExpr(e) + "))\n";
}

View File

@ -17,10 +17,10 @@
#pragma once
#include <libsolidity/formal/SolverInterface.h>
#include <libsmtutil/SolverInterface.h>
#include <libsolidity/interface/ReadFile.h>
#include <liblangutil/Exceptions.h>
#include <libsolutil/Common.h>
#include <libsolutil/FixedHash.h>
@ -31,7 +31,7 @@
#include <string>
#include <vector>
namespace solidity::frontend::smt
namespace solidity::smtutil
{
class SMTLib2Interface: public SolverInterface, public boost::noncopyable
@ -39,7 +39,7 @@ class SMTLib2Interface: public SolverInterface, public boost::noncopyable
public:
explicit SMTLib2Interface(
std::map<util::h256, std::string> const& _queryResponses,
ReadCallback::Callback _smtCallback
frontend::ReadCallback::Callback _smtCallback
);
void reset() override;
@ -49,13 +49,13 @@ public:
void declareVariable(std::string const&, SortPointer const&) override;
void addAssertion(smt::Expression const& _expr) override;
std::pair<CheckResult, std::vector<std::string>> check(std::vector<smt::Expression> const& _expressionsToEvaluate) override;
void addAssertion(Expression const& _expr) override;
std::pair<CheckResult, std::vector<std::string>> check(std::vector<Expression> const& _expressionsToEvaluate) override;
std::vector<std::string> unhandledQueries() override { return m_unhandledQueries; }
// Used by CHCSmtLib2Interface
std::string toSExpr(smt::Expression const& _expr);
std::string toSExpr(Expression const& _expr);
std::string toSmtLibSort(Sort const& _sort);
std::string toSmtLibSort(std::vector<SortPointer> const& _sort);
@ -66,7 +66,7 @@ private:
void write(std::string _data);
std::string checkSatAndGetValuesCommand(std::vector<smt::Expression> const& _expressionsToEvaluate);
std::string checkSatAndGetValuesCommand(std::vector<Expression> const& _expressionsToEvaluate);
std::vector<std::string> parseValues(std::string::const_iterator _start, std::string::const_iterator _end);
/// Communicates with the solver via the callback. Throws SMTSolverError on error.
@ -79,7 +79,7 @@ private:
std::map<util::h256, std::string> const& m_queryResponses;
std::vector<std::string> m_unhandledQueries;
ReadCallback::Callback m_smtCallback;
frontend::ReadCallback::Callback m_smtCallback;
};
}

View File

@ -15,36 +15,36 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libsolidity/formal/SMTPortfolio.h>
#include <libsmtutil/SMTPortfolio.h>
#ifdef HAVE_Z3
#include <libsolidity/formal/Z3Interface.h>
#include <libsmtutil/Z3Interface.h>
#endif
#ifdef HAVE_CVC4
#include <libsolidity/formal/CVC4Interface.h>
#include <libsmtutil/CVC4Interface.h>
#endif
#include <libsolidity/formal/SMTLib2Interface.h>
#include <libsmtutil/SMTLib2Interface.h>
using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
using namespace solidity::frontend::smt;
using namespace solidity::smtutil;
SMTPortfolio::SMTPortfolio(
map<h256, string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback,
frontend::ReadCallback::Callback const& _smtCallback,
[[maybe_unused]] SMTSolverChoice _enabledSolvers
)
{
m_solvers.emplace_back(make_unique<smt::SMTLib2Interface>(_smtlib2Responses, _smtCallback));
m_solvers.emplace_back(make_unique<SMTLib2Interface>(_smtlib2Responses, _smtCallback));
#ifdef HAVE_Z3
if (_enabledSolvers.z3)
m_solvers.emplace_back(make_unique<smt::Z3Interface>());
m_solvers.emplace_back(make_unique<Z3Interface>());
#endif
#ifdef HAVE_CVC4
if (_enabledSolvers.cvc4)
m_solvers.emplace_back(make_unique<smt::CVC4Interface>());
m_solvers.emplace_back(make_unique<CVC4Interface>());
#endif
}
@ -68,12 +68,12 @@ void SMTPortfolio::pop()
void SMTPortfolio::declareVariable(string const& _name, SortPointer const& _sort)
{
solAssert(_sort, "");
smtAssert(_sort, "");
for (auto const& s: m_solvers)
s->declareVariable(_name, _sort);
}
void SMTPortfolio::addAssertion(smt::Expression const& _expr)
void SMTPortfolio::addAssertion(Expression const& _expr)
{
for (auto const& s: m_solvers)
s->addAssertion(_expr);
@ -109,7 +109,7 @@ void SMTPortfolio::addAssertion(smt::Expression const& _expr)
*
* If all solvers return ERROR, the result is ERROR.
*/
pair<CheckResult, vector<string>> SMTPortfolio::check(vector<smt::Expression> const& _expressionsToEvaluate)
pair<CheckResult, vector<string>> SMTPortfolio::check(vector<Expression> const& _expressionsToEvaluate)
{
CheckResult lastResult = CheckResult::ERROR;
vector<string> finalValues;
@ -141,8 +141,8 @@ vector<string> SMTPortfolio::unhandledQueries()
{
// This code assumes that the constructor guarantees that
// SmtLib2Interface is in position 0.
solAssert(!m_solvers.empty(), "");
solAssert(dynamic_cast<smt::SMTLib2Interface*>(m_solvers.front().get()), "");
smtAssert(!m_solvers.empty(), "");
smtAssert(dynamic_cast<SMTLib2Interface*>(m_solvers.front().get()), "");
return m_solvers.front()->unhandledQueries();
}

View File

@ -18,7 +18,7 @@
#pragma once
#include <libsolidity/formal/SolverInterface.h>
#include <libsmtutil/SolverInterface.h>
#include <libsolidity/interface/ReadFile.h>
#include <libsolutil/FixedHash.h>
@ -26,7 +26,7 @@
#include <map>
#include <vector>
namespace solidity::frontend::smt
namespace solidity::smtutil
{
/**
@ -40,7 +40,7 @@ class SMTPortfolio: public SolverInterface, public boost::noncopyable
public:
SMTPortfolio(
std::map<util::h256, std::string> const& _smtlib2Responses,
ReadCallback::Callback const& _smtCallback,
frontend::ReadCallback::Callback const& _smtCallback,
SMTSolverChoice _enabledSolvers
);
@ -51,18 +51,18 @@ public:
void declareVariable(std::string const&, SortPointer const&) override;
void addAssertion(smt::Expression const& _expr) override;
void addAssertion(Expression const& _expr) override;
std::pair<CheckResult, std::vector<std::string>> check(std::vector<smt::Expression> const& _expressionsToEvaluate) override;
std::pair<CheckResult, std::vector<std::string>> check(std::vector<Expression> const& _expressionsToEvaluate) override;
std::vector<std::string> unhandledQueries() override;
unsigned solvers() override { return m_solvers.size(); }
private:
static bool solverAnswered(CheckResult result);
std::vector<std::unique_ptr<smt::SolverInterface>> m_solvers;
std::vector<std::unique_ptr<SolverInterface>> m_solvers;
std::vector<smt::Expression> m_assertions;
std::vector<Expression> m_assertions;
};
}

View File

@ -17,21 +17,19 @@
#pragma once
#include <libsolidity/formal/Sorts.h>
#include <libsmtutil/Exceptions.h>
#include <libsmtutil/Sorts.h>
#include <libsolidity/ast/Types.h>
#include <libsolidity/interface/ReadFile.h>
#include <liblangutil/Exceptions.h>
#include <libsolutil/Common.h>
#include <libsolutil/Exceptions.h>
#include <boost/noncopyable.hpp>
#include <cstdio>
#include <map>
#include <memory>
#include <string>
#include <vector>
namespace solidity::frontend::smt
namespace solidity::smtutil
{
struct SMTSolverChoice
@ -54,16 +52,13 @@ enum class CheckResult
SATISFIABLE, UNSATISFIABLE, UNKNOWN, CONFLICTING, ERROR
};
// Forward declaration.
SortPointer smtSort(Type const& _type);
/// C++ representation of an SMTLIB2 expression.
class Expression
{
friend class SolverInterface;
public:
explicit Expression(bool _v): Expression(_v ? "true" : "false", Kind::Bool) {}
explicit Expression(frontend::TypePointer _type): Expression(_type->toString(), {}, std::make_shared<SortSort>(smtSort(*_type))) {}
explicit Expression(std::shared_ptr<SortSort> _sort, std::string _name = ""): Expression(std::move(_name), {}, _sort) {}
Expression(size_t _number): Expression(std::to_string(_number), Kind::Int) {}
Expression(u256 const& _number): Expression(_number.str(), Kind::Int) {}
Expression(s256 const& _number): Expression(_number.str(), Kind::Int) {}
@ -76,6 +71,13 @@ public:
bool hasCorrectArity() const
{
if (name == "tuple_constructor")
{
auto tupleSort = std::dynamic_pointer_cast<TupleSort>(sort);
smtAssert(tupleSort, "");
return arguments.size() == tupleSort->components.size();
}
static std::map<std::string, unsigned> const operatorsArity{
{"ite", 3},
{"not", 1},
@ -92,6 +94,9 @@ public:
{"*", 2},
{"/", 2},
{"mod", 2},
{"bvand", 2},
{"int2bv", 2},
{"bv2int", 1},
{"select", 2},
{"store", 3},
{"const_array", 2},
@ -102,7 +107,7 @@ public:
static Expression ite(Expression _condition, Expression _trueValue, Expression _falseValue)
{
solAssert(*_trueValue.sort == *_falseValue.sort, "");
smtAssert(*_trueValue.sort == *_falseValue.sort, "");
SortPointer sort = _trueValue.sort;
return Expression("ite", std::vector<Expression>{
std::move(_condition), std::move(_trueValue), std::move(_falseValue)
@ -122,11 +127,11 @@ public:
/// select is the SMT representation of an array index access.
static Expression select(Expression _array, Expression _index)
{
solAssert(_array.sort->kind == Kind::Array, "");
smtAssert(_array.sort->kind == Kind::Array, "");
std::shared_ptr<ArraySort> arraySort = std::dynamic_pointer_cast<ArraySort>(_array.sort);
solAssert(arraySort, "");
solAssert(_index.sort, "");
solAssert(*arraySort->domain == *_index.sort, "");
smtAssert(arraySort, "");
smtAssert(_index.sort, "");
smtAssert(*arraySort->domain == *_index.sort, "");
return Expression(
"select",
std::vector<Expression>{std::move(_array), std::move(_index)},
@ -138,13 +143,12 @@ public:
/// The function is pure and returns the modified array.
static Expression store(Expression _array, Expression _index, Expression _element)
{
solAssert(_array.sort->kind == Kind::Array, "");
std::shared_ptr<ArraySort> arraySort = std::dynamic_pointer_cast<ArraySort>(_array.sort);
solAssert(arraySort, "");
solAssert(_index.sort, "");
solAssert(_element.sort, "");
solAssert(*arraySort->domain == *_index.sort, "");
solAssert(*arraySort->range == *_element.sort, "");
auto arraySort = std::dynamic_pointer_cast<ArraySort>(_array.sort);
smtAssert(arraySort, "");
smtAssert(_index.sort, "");
smtAssert(_element.sort, "");
smtAssert(*arraySort->domain == *_index.sort, "");
smtAssert(*arraySort->range == *_element.sort, "");
return Expression(
"store",
std::vector<Expression>{std::move(_array), std::move(_index), std::move(_element)},
@ -154,12 +158,12 @@ public:
static Expression const_array(Expression _sort, Expression _value)
{
solAssert(_sort.sort->kind == Kind::Sort, "");
smtAssert(_sort.sort->kind == Kind::Sort, "");
auto sortSort = std::dynamic_pointer_cast<SortSort>(_sort.sort);
auto arraySort = std::dynamic_pointer_cast<ArraySort>(sortSort->inner);
solAssert(sortSort && arraySort, "");
solAssert(_value.sort, "");
solAssert(*arraySort->range == *_value.sort, "");
smtAssert(sortSort && arraySort, "");
smtAssert(_value.sort, "");
smtAssert(*arraySort->range == *_value.sort, "");
return Expression(
"const_array",
std::vector<Expression>{std::move(_sort), std::move(_value)},
@ -169,10 +173,10 @@ public:
static Expression tuple_get(Expression _tuple, size_t _index)
{
solAssert(_tuple.sort->kind == Kind::Tuple, "");
smtAssert(_tuple.sort->kind == Kind::Tuple, "");
std::shared_ptr<TupleSort> tupleSort = std::dynamic_pointer_cast<TupleSort>(_tuple.sort);
solAssert(tupleSort, "");
solAssert(_index < tupleSort->components.size(), "");
smtAssert(tupleSort, "");
smtAssert(_index < tupleSort->components.size(), "");
return Expression(
"tuple_get",
std::vector<Expression>{std::move(_tuple), Expression(_index)},
@ -180,6 +184,46 @@ public:
);
}
static Expression tuple_constructor(Expression _tuple, std::vector<Expression> _arguments)
{
smtAssert(_tuple.sort->kind == Kind::Sort, "");
auto sortSort = std::dynamic_pointer_cast<SortSort>(_tuple.sort);
auto tupleSort = std::dynamic_pointer_cast<TupleSort>(sortSort->inner);
smtAssert(tupleSort, "");
smtAssert(_arguments.size() == tupleSort->components.size(), "");
return Expression(
"tuple_constructor",
std::move(_arguments),
tupleSort
);
}
static Expression int2bv(Expression _n, size_t _size)
{
smtAssert(_n.sort->kind == Kind::Int, "");
std::shared_ptr<IntSort> intSort = std::dynamic_pointer_cast<IntSort>(_n.sort);
smtAssert(intSort, "");
smtAssert(_size <= 256, "");
return Expression(
"int2bv",
std::vector<Expression>{std::move(_n), Expression(_size)},
std::make_shared<BitVectorSort>(_size)
);
}
static Expression bv2int(Expression _bv, bool _signed = false)
{
smtAssert(_bv.sort->kind == Kind::BitVector, "");
std::shared_ptr<BitVectorSort> bvSort = std::dynamic_pointer_cast<BitVectorSort>(_bv.sort);
smtAssert(bvSort, "");
smtAssert(bvSort->size <= 256, "");
return Expression(
"bv2int",
std::vector<Expression>{std::move(_bv)},
SortProvider::intSort(_signed)
);
}
friend Expression operator!(Expression _a)
{
return Expression("not", std::move(_a), Kind::Bool);
@ -236,14 +280,19 @@ public:
{
return Expression("mod", std::move(_a), std::move(_b), Kind::Int);
}
friend Expression operator&(Expression _a, Expression _b)
{
auto bvSort = _a.sort;
return Expression("bvand", {std::move(_a), std::move(_b)}, bvSort);
}
Expression operator()(std::vector<Expression> _arguments) const
{
solAssert(
smtAssert(
sort->kind == Kind::Function,
"Attempted function application to non-function."
);
auto fSort = dynamic_cast<FunctionSort const*>(sort.get());
solAssert(fSort, "");
smtAssert(fSort, "");
return Expression(name, std::move(_arguments), fSort->codomain);
}
@ -281,7 +330,7 @@ public:
Expression newVariable(std::string _name, SortPointer const& _sort)
{
// Subclasses should do something here
solAssert(_sort, "");
smtAssert(_sort, "");
declareVariable(_name, _sort);
return Expression(std::move(_name), {}, _sort);
}

37
libsmtutil/Sorts.cpp Normal file
View File

@ -0,0 +1,37 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libsmtutil/Sorts.h>
using namespace std;
namespace solidity::smtutil
{
shared_ptr<Sort> const SortProvider::boolSort{make_shared<Sort>(Kind::Bool)};
shared_ptr<IntSort> const SortProvider::uintSort{make_shared<IntSort>(false)};
shared_ptr<IntSort> const SortProvider::sintSort{make_shared<IntSort>(true)};
shared_ptr<IntSort> SortProvider::intSort(bool _signed)
{
if (_signed)
return sintSort;
return uintSort;
}
}

View File

@ -17,20 +17,21 @@
#pragma once
#include <liblangutil/Exceptions.h>
#include <libsmtutil/Exceptions.h>
#include <libsolutil/Common.h>
#include <libsolutil/Exceptions.h>
#include <memory>
#include <vector>
namespace solidity::frontend::smt
namespace solidity::smtutil
{
enum class Kind
{
Int,
Bool,
BitVector,
Function,
Array,
Sort,
@ -48,6 +49,36 @@ struct Sort
};
using SortPointer = std::shared_ptr<Sort>;
struct IntSort: public Sort
{
IntSort(bool _signed):
Sort(Kind::Int),
isSigned(_signed)
{}
bool operator==(IntSort const& _other) const
{
return Sort::operator==(_other) && isSigned == _other.isSigned;
}
bool isSigned;
};
struct BitVectorSort: public Sort
{
BitVectorSort(unsigned _size):
Sort(Kind::BitVector),
size(_size)
{}
bool operator==(BitVectorSort const& _other) const
{
return Sort::operator==(_other) && size == _other.size;
}
unsigned size;
};
struct FunctionSort: public Sort
{
FunctionSort(std::vector<SortPointer> _domain, SortPointer _codomain):
@ -57,7 +88,7 @@ struct FunctionSort: public Sort
if (!Sort::operator==(_other))
return false;
auto _otherFunction = dynamic_cast<FunctionSort const*>(&_other);
solAssert(_otherFunction, "");
smtAssert(_otherFunction, "");
if (domain.size() != _otherFunction->domain.size())
return false;
if (!std::equal(
@ -67,8 +98,8 @@ struct FunctionSort: public Sort
[&](SortPointer _a, SortPointer _b) { return *_a == *_b; }
))
return false;
solAssert(codomain, "");
solAssert(_otherFunction->codomain, "");
smtAssert(codomain, "");
smtAssert(_otherFunction->codomain, "");
return *codomain == *_otherFunction->codomain;
}
@ -87,11 +118,11 @@ struct ArraySort: public Sort
if (!Sort::operator==(_other))
return false;
auto _otherArray = dynamic_cast<ArraySort const*>(&_other);
solAssert(_otherArray, "");
solAssert(_otherArray->domain, "");
solAssert(_otherArray->range, "");
solAssert(domain, "");
solAssert(range, "");
smtAssert(_otherArray, "");
smtAssert(_otherArray->domain, "");
smtAssert(_otherArray->range, "");
smtAssert(domain, "");
smtAssert(range, "");
return *domain == *_otherArray->domain && *range == *_otherArray->range;
}
@ -107,9 +138,9 @@ struct SortSort: public Sort
if (!Sort::operator==(_other))
return false;
auto _otherSort = dynamic_cast<SortSort const*>(&_other);
solAssert(_otherSort, "");
solAssert(_otherSort->inner, "");
solAssert(inner, "");
smtAssert(_otherSort, "");
smtAssert(_otherSort->inner, "");
smtAssert(inner, "");
return *inner == *_otherSort->inner;
}
@ -134,7 +165,7 @@ struct TupleSort: public Sort
if (!Sort::operator==(_other))
return false;
auto _otherTuple = dynamic_cast<TupleSort const*>(&_other);
solAssert(_otherTuple, "");
smtAssert(_otherTuple, "");
if (name != _otherTuple->name)
return false;
if (members != _otherTuple->members)
@ -160,7 +191,9 @@ struct TupleSort: public Sort
struct SortProvider
{
static std::shared_ptr<Sort> const boolSort;
static std::shared_ptr<Sort> const intSort;
static std::shared_ptr<IntSort> const uintSort;
static std::shared_ptr<IntSort> const sintSort;
static std::shared_ptr<IntSort> intSort(bool _signed = false);
};
}

View File

@ -15,14 +15,13 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libsolidity/formal/Z3CHCInterface.h>
#include <libsmtutil/Z3CHCInterface.h>
#include <liblangutil/Exceptions.h>
#include <libsolutil/CommonIO.h>
using namespace std;
using namespace solidity;
using namespace solidity::frontend::smt;
using namespace solidity::smtutil;
Z3CHCInterface::Z3CHCInterface():
m_z3Interface(make_unique<Z3Interface>()),
@ -48,7 +47,7 @@ Z3CHCInterface::Z3CHCInterface():
void Z3CHCInterface::declareVariable(string const& _name, SortPointer const& _sort)
{
solAssert(_sort, "");
smtAssert(_sort, "");
m_z3Interface->declareVariable(_name, _sort);
}

View File

@ -21,10 +21,10 @@
#pragma once
#include <libsolidity/formal/CHCSolverInterface.h>
#include <libsolidity/formal/Z3Interface.h>
#include <libsmtutil/CHCSolverInterface.h>
#include <libsmtutil/Z3Interface.h>
namespace solidity::frontend::smt
namespace solidity::smtutil
{
class Z3CHCInterface: public CHCSolverInterface

View File

@ -15,13 +15,12 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libsolidity/formal/Z3Interface.h>
#include <libsmtutil/Z3Interface.h>
#include <liblangutil/Exceptions.h>
#include <libsolutil/CommonIO.h>
using namespace std;
using namespace solidity::frontend::smt;
using namespace solidity::smtutil;
Z3Interface::Z3Interface():
m_solver(m_context)
@ -50,7 +49,7 @@ void Z3Interface::pop()
void Z3Interface::declareVariable(string const& _name, SortPointer const& _sort)
{
solAssert(_sort, "");
smtAssert(_sort, "");
if (_sort->kind == Kind::Function)
declareFunction(_name, *_sort);
else if (m_constants.count(_name))
@ -61,7 +60,7 @@ void Z3Interface::declareVariable(string const& _name, SortPointer const& _sort)
void Z3Interface::declareFunction(string const& _name, Sort const& _sort)
{
solAssert(_sort.kind == smt::Kind::Function, "");
smtAssert(_sort.kind == Kind::Function, "");
FunctionSort fSort = dynamic_cast<FunctionSort const&>(_sort);
if (m_functions.count(_name))
m_functions.at(_name) = m_context.function(_name.c_str(), z3Sort(fSort.domain), z3Sort(*fSort.codomain));
@ -124,7 +123,7 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
return m_functions.at(n)(arguments);
else if (m_constants.count(n))
{
solAssert(arguments.empty(), "");
smtAssert(arguments.empty(), "");
return m_constants.at(n);
}
else if (arguments.empty())
@ -136,7 +135,7 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
else if (_expr.sort->kind == Kind::Sort)
{
auto sortSort = dynamic_pointer_cast<SortSort>(_expr.sort);
solAssert(sortSort, "");
smtAssert(sortSort, "");
return m_context.constant(n.c_str(), z3Sort(*sortSort->inner));
}
else
@ -146,11 +145,11 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
}
catch (z3::exception const& _e)
{
solAssert(false, _e.msg());
smtAssert(false, _e.msg());
}
}
solAssert(_expr.hasCorrectArity(), "");
smtAssert(_expr.hasCorrectArity(), "");
if (n == "ite")
return z3::ite(arguments[0], arguments[1], arguments[2]);
else if (n == "not")
@ -181,6 +180,19 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
return arguments[0] / arguments[1];
else if (n == "mod")
return z3::mod(arguments[0], arguments[1]);
else if (n == "bvand")
return arguments[0] & arguments[1];
else if (n == "int2bv")
{
size_t size = std::stoi(_expr.arguments[1].name);
return z3::int2bv(size, arguments[0]);
}
else if (n == "bv2int")
{
auto intSort = dynamic_pointer_cast<IntSort>(_expr.sort);
smtAssert(intSort, "");
return z3::bv2int(arguments[0], intSort->isSigned);
}
else if (n == "select")
return z3::select(arguments[0], arguments[1]);
else if (n == "store")
@ -188,25 +200,34 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
else if (n == "const_array")
{
shared_ptr<SortSort> sortSort = std::dynamic_pointer_cast<SortSort>(_expr.arguments[0].sort);
solAssert(sortSort, "");
smtAssert(sortSort, "");
auto arraySort = dynamic_pointer_cast<ArraySort>(sortSort->inner);
solAssert(arraySort && arraySort->domain, "");
smtAssert(arraySort && arraySort->domain, "");
return z3::const_array(z3Sort(*arraySort->domain), arguments[1]);
}
else if (n == "tuple_get")
{
size_t index = std::stoi(_expr.arguments[1].name);
size_t index = stoul(_expr.arguments[1].name);
return z3::func_decl(m_context, Z3_get_tuple_sort_field_decl(m_context, z3Sort(*_expr.arguments[0].sort), index))(arguments[0]);
}
else if (n == "tuple_constructor")
{
auto constructor = z3::func_decl(m_context, Z3_get_tuple_sort_mk_decl(m_context, z3Sort(*_expr.sort)));
smtAssert(constructor.arity() == arguments.size(), "");
z3::expr_vector args(m_context);
for (auto const& arg: arguments)
args.push_back(arg);
return constructor(args);
}
solAssert(false, "");
smtAssert(false, "");
}
catch (z3::exception const& _e)
{
solAssert(false, _e.msg());
smtAssert(false, _e.msg());
}
solAssert(false, "");
smtAssert(false, "");
}
z3::sort Z3Interface::z3Sort(Sort const& _sort)
@ -247,7 +268,7 @@ z3::sort Z3Interface::z3Sort(Sort const& _sort)
default:
break;
}
solAssert(false, "");
smtAssert(false, "");
// Cannot be reached.
return m_context.int_sort();
}

View File

@ -17,11 +17,11 @@
#pragma once
#include <libsolidity/formal/SolverInterface.h>
#include <libsmtutil/SolverInterface.h>
#include <boost/noncopyable.hpp>
#include <z3++.h>
namespace solidity::frontend::smt
namespace solidity::smtutil
{
class Z3Interface: public SolverInterface, public boost::noncopyable
@ -55,8 +55,8 @@ public:
private:
void declareFunction(std::string const& _name, Sort const& _sort);
z3::sort z3Sort(smt::Sort const& _sort);
z3::sort_vector z3Sort(std::vector<smt::SortPointer> const& _sorts);
z3::sort z3Sort(Sort const& _sort);
z3::sort_vector z3Sort(std::vector<SortPointer> const& _sorts);
z3::context m_context;
z3::solver m_solver;

View File

@ -79,6 +79,8 @@ set(sources
codegen/ReturnInfo.cpp
codegen/YulUtilFunctions.h
codegen/YulUtilFunctions.cpp
codegen/ir/Common.cpp
codegen/ir/Common.h
codegen/ir/IRGenerator.cpp
codegen/ir/IRGenerator.h
codegen/ir/IRGeneratorForStatements.cpp
@ -92,22 +94,12 @@ set(sources
formal/BMC.h
formal/CHC.cpp
formal/CHC.h
formal/CHCSmtLib2Interface.cpp
formal/CHCSmtLib2Interface.h
formal/CHCSolverInterface.h
formal/EncodingContext.cpp
formal/EncodingContext.h
formal/ModelChecker.cpp
formal/ModelChecker.h
formal/SMTEncoder.cpp
formal/SMTEncoder.h
formal/SMTLib2Interface.cpp
formal/SMTLib2Interface.h
formal/SMTPortfolio.cpp
formal/SMTPortfolio.h
formal/SolverInterface.h
formal/Sorts.cpp
formal/Sorts.h
formal/SSAVariable.cpp
formal/SSAVariable.h
formal/SymbolicState.cpp
@ -142,36 +134,6 @@ set(sources
parsing/Token.h
)
find_package(Z3 4.6.0)
if (${Z3_FOUND})
add_definitions(-DHAVE_Z3)
message("Z3 SMT solver found. This enables optional SMT checking with Z3.")
set(z3_SRCS formal/Z3Interface.cpp formal/Z3Interface.h formal/Z3CHCInterface.cpp formal/Z3CHCInterface.h)
else()
set(z3_SRCS)
endif()
add_library(solidity ${sources})
target_link_libraries(solidity PUBLIC yul evmasm langutil smtutil solutil Boost::boost)
find_package(CVC4 QUIET)
if (${CVC4_FOUND})
add_definitions(-DHAVE_CVC4)
message("CVC4 SMT solver found. This enables optional SMT checking with CVC4.")
set(cvc4_SRCS formal/CVC4Interface.cpp formal/CVC4Interface.h)
else()
set(cvc4_SRCS)
endif()
if (NOT (${Z3_FOUND} OR ${CVC4_FOUND}))
message("No SMT solver found (or it has been forcefully disabled). Optional SMT checking will not be available.\
\nPlease install Z3 or CVC4 or remove the option disabling them (USE_Z3, USE_CVC4).")
endif()
add_library(solidity ${sources} ${z3_SRCS} ${cvc4_SRCS})
target_link_libraries(solidity PUBLIC yul evmasm langutil solutil Boost::boost)
if (${Z3_FOUND})
target_link_libraries(solidity PUBLIC z3::libz3)
endif()
if (${CVC4_FOUND})
target_link_libraries(solidity PUBLIC CVC4::CVC4)
endif()

View File

@ -41,8 +41,8 @@ bool hasEqualNameAndParameters(T const& _a, B const& _b)
{
return
_a.name() == _b.name() &&
FunctionType(_a).asCallableFunction(false)->hasEqualParameterTypes(
*FunctionType(_b).asCallableFunction(false)
FunctionType(_a).asExternallyCallableFunction(false)->hasEqualParameterTypes(
*FunctionType(_b).asExternallyCallableFunction(false)
);
}
@ -345,7 +345,7 @@ void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _c
// under non error circumstances this should be true
if (functionType->interfaceFunctionType())
externalDeclarations[functionType->externalSignature()].emplace_back(
f, functionType->asCallableFunction(false)
f, functionType->asExternallyCallableFunction(false)
);
}
for (VariableDeclaration const* v: contract->stateVariables())
@ -355,7 +355,7 @@ void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _c
// under non error circumstances this should be true
if (functionType->interfaceFunctionType())
externalDeclarations[functionType->externalSignature()].emplace_back(
v, functionType->asCallableFunction(false)
v, functionType->asExternallyCallableFunction(false)
);
}
}

View File

@ -94,7 +94,10 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
case VariableOccurrence::Kind::Return:
if (unassignedVariables.count(&variableOccurrence.declaration()))
{
if (variableOccurrence.declaration().type()->dataStoredIn(DataLocation::Storage))
if (
variableOccurrence.declaration().type()->dataStoredIn(DataLocation::Storage) ||
variableOccurrence.declaration().type()->dataStoredIn(DataLocation::CallData)
)
// Merely store the unassigned access. We do not generate an error right away, since this
// path might still always revert. It is only an error if this is propagated to the exit
// node of the function (i.e. there is a path with an uninitialized access).
@ -135,13 +138,16 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
if (variableOccurrence->occurrence())
ssl.append("The variable was declared here.", variableOccurrence->declaration().location());
bool isStorage = variableOccurrence->declaration().type()->dataStoredIn(DataLocation::Storage);
m_errorReporter.typeError(
3464_error,
variableOccurrence->occurrence() ?
*variableOccurrence->occurrence() :
variableOccurrence->declaration().location(),
ssl,
string("This variable is of storage pointer type and can be ") +
"This variable is of " +
string(isStorage ? "storage" : "calldata") +
" pointer type and can be " +
(variableOccurrence->kind() == VariableOccurrence::Kind::Return ? "returned" : "accessed") +
" without prior assignment, which would lead to undefined behaviour."
);

View File

@ -50,7 +50,8 @@ bool DeclarationTypeChecker::visit(ElementaryTypeName const& _typeName)
_typeName.annotation().type = TypeProvider::address();
break;
default:
typeError(
m_errorReporter.typeError(
2311_error,
_typeName.location(),
"Address types can only be payable or non-payable."
);
@ -102,7 +103,11 @@ bool DeclarationTypeChecker::visit(StructDefinition const& _struct)
auto visitor = [&](StructDefinition const& _struct, auto& _cycleDetector, size_t _depth)
{
if (_depth >= 256)
fatalDeclarationError(_struct.location(), "Struct definition exhausts cyclic dependency validator.");
m_errorReporter.fatalDeclarationError(
5651_error,
_struct.location(),
"Struct definition exhausts cyclic dependency validator."
);
for (ASTPointer<VariableDeclaration> const& member: _struct.members())
{
@ -119,7 +124,7 @@ bool DeclarationTypeChecker::visit(StructDefinition const& _struct)
}
};
if (util::CycleDetector<StructDefinition>(visitor).run(_struct) != nullptr)
fatalTypeError(_struct.location(), "Recursive struct definition.");
m_errorReporter.fatalTypeError(2046_error, _struct.location(), "Recursive struct definition.");
return false;
}
@ -145,9 +150,14 @@ void DeclarationTypeChecker::endVisit(UserDefinedTypeName const& _typeName)
else
{
_typeName.annotation().type = TypeProvider::emptyTuple();
fatalTypeError(_typeName.location(), "Name has to refer to a struct, enum or contract.");
m_errorReporter.fatalTypeError(
5172_error,
_typeName.location(),
"Name has to refer to a struct, enum or contract."
);
}
}
bool DeclarationTypeChecker::visit(FunctionTypeName const& _typeName)
{
if (_typeName.annotation().type)
@ -165,18 +175,27 @@ bool DeclarationTypeChecker::visit(FunctionTypeName const& _typeName)
case Visibility::External:
break;
default:
fatalTypeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\".");
m_errorReporter.fatalTypeError(
6012_error,
_typeName.location(),
"Invalid visibility, can only be \"external\" or \"internal\"."
);
return false;
}
if (_typeName.isPayable() && _typeName.visibility() != Visibility::External)
{
fatalTypeError(_typeName.location(), "Only external function types can be payable.");
m_errorReporter.fatalTypeError(
7415_error,
_typeName.location(),
"Only external function types can be payable."
);
return false;
}
_typeName.annotation().type = TypeProvider::function(_typeName);
return false;
}
void DeclarationTypeChecker::endVisit(Mapping const& _mapping)
{
if (_mapping.annotation().type)
@ -226,7 +245,11 @@ void DeclarationTypeChecker::endVisit(ArrayTypeName const& _typeName)
return;
}
if (baseType->storageBytes() == 0)
fatalTypeError(_typeName.baseType().location(), "Illegal base type of storage size zero for array.");
m_errorReporter.fatalTypeError(
6493_error,
_typeName.baseType().location(),
"Illegal base type of storage size zero for array."
);
if (Expression const* length = _typeName.length())
{
TypePointer& lengthTypeGeneric = length->annotation().type;
@ -235,13 +258,17 @@ void DeclarationTypeChecker::endVisit(ArrayTypeName const& _typeName)
RationalNumberType const* lengthType = dynamic_cast<RationalNumberType const*>(lengthTypeGeneric);
u256 lengthValue = 0;
if (!lengthType || !lengthType->mobileType())
typeError(length->location(), "Invalid array length, expected integer literal or constant expression.");
m_errorReporter.typeError(
5462_error,
length->location(),
"Invalid array length, expected integer literal or constant expression."
);
else if (lengthType->isZero())
typeError(length->location(), "Array with zero length specified.");
m_errorReporter.typeError(1406_error, length->location(), "Array with zero length specified.");
else if (lengthType->isFractional())
typeError(length->location(), "Array with fractional length specified.");
m_errorReporter.typeError(3208_error, length->location(), "Array with fractional length specified.");
else if (lengthType->isNegative())
typeError(length->location(), "Array with negative length specified.");
m_errorReporter.typeError(3658_error, length->location(), "Array with negative length specified.");
else
lengthValue = lengthType->literalValue(nullptr);
_typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType, lengthValue);
@ -249,15 +276,24 @@ void DeclarationTypeChecker::endVisit(ArrayTypeName const& _typeName)
else
_typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType);
}
void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
{
if (_variable.annotation().type)
return;
if (_variable.isConstant() && !_variable.isStateVariable())
m_errorReporter.declarationError(1788_error, _variable.location(), "The \"constant\" keyword can only be used for state variables.");
m_errorReporter.declarationError(
1788_error,
_variable.location(),
"The \"constant\" keyword can only be used for state variables."
);
if (_variable.immutable() && !_variable.isStateVariable())
m_errorReporter.declarationError(8297_error, _variable.location(), "The \"immutable\" keyword can only be used for state variables.");
m_errorReporter.declarationError(
8297_error,
_variable.location(),
"The \"immutable\" keyword can only be used for state variables."
);
if (!_variable.typeName())
{
@ -309,7 +345,7 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
errorString += " for variable";
}
errorString += ", but " + locationToString(varLoc) + " was given.";
typeError(_variable.location(), errorString);
m_errorReporter.typeError(6651_error, _variable.location(), errorString);
solAssert(!allowedDataLocations.empty(), "");
varLoc = *allowedDataLocations.begin();
@ -359,24 +395,9 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
}
void DeclarationTypeChecker::typeError(SourceLocation const& _location, string const& _description)
{
m_errorReporter.typeError(2311_error, _location, _description);
}
void DeclarationTypeChecker::fatalTypeError(SourceLocation const& _location, string const& _description)
{
m_errorReporter.fatalTypeError(5651_error, _location, _description);
}
void DeclarationTypeChecker::fatalDeclarationError(SourceLocation const& _location, string const& _description)
{
m_errorReporter.fatalDeclarationError(2046_error, _location, _description);
}
bool DeclarationTypeChecker::check(ASTNode const& _node)
{
unsigned errorCount = m_errorReporter.errorCount();
auto watcher = m_errorReporter.errorWatcher();
_node.accept(*this);
return m_errorReporter.errorCount() == errorCount;
return watcher.ok();
}

View File

@ -59,15 +59,6 @@ private:
void endVisit(VariableDeclaration const& _variable) override;
bool visit(StructDefinition const& _struct) override;
/// Adds a new error to the list of errors.
void typeError(langutil::SourceLocation const& _location, std::string const& _description);
/// Adds a new error to the list of errors and throws to abort reference resolving.
void fatalTypeError(langutil::SourceLocation const& _location, std::string const& _description);
/// Adds a new error to the list of errors and throws to abort reference resolving.
void fatalDeclarationError(langutil::SourceLocation const& _location, std::string const& _description);
langutil::ErrorReporter& m_errorReporter;
langutil::EVMVersion m_evmVersion;
bool m_insideFunctionType = false;

View File

@ -34,10 +34,9 @@ using namespace solidity::frontend;
bool DocStringAnalyser::analyseDocStrings(SourceUnit const& _sourceUnit)
{
m_errorOccured = false;
auto errorWatcher = m_errorReporter.errorWatcher();
_sourceUnit.accept(*this);
return !m_errorOccured;
return errorWatcher.ok();
}
bool DocStringAnalyser::visit(ContractDefinition const& _contract)
@ -57,6 +56,33 @@ bool DocStringAnalyser::visit(FunctionDefinition const& _function)
return true;
}
bool DocStringAnalyser::visit(VariableDeclaration const& _variable)
{
if (_variable.isStateVariable())
{
static set<string> const validPublicTags = set<string>{"dev", "notice", "return", "title", "author"};
if (_variable.isPublic())
parseDocStrings(_variable, _variable.annotation(), validPublicTags, "public state variables");
else
{
parseDocStrings(_variable, _variable.annotation(), validPublicTags, "non-public state variables");
if (_variable.annotation().docTags.count("notice") > 0)
m_errorReporter.warning(
7816_error, _variable.documentation()->location(),
"Documentation tag on non-public state variables will be disallowed in 0.7.0. "
"You will need to use the @dev tag explicitly."
);
}
if (_variable.annotation().docTags.count("title") > 0 || _variable.annotation().docTags.count("author") > 0)
m_errorReporter.warning(
8532_error, _variable.documentation()->location(),
"Documentation tag @title and @author is only allowed on contract definitions. "
"It will be disallowed in 0.7.0."
);
}
return false;
}
bool DocStringAnalyser::visit(ModifierDefinition const& _modifier)
{
handleCallable(_modifier, _modifier, _modifier.annotation());
@ -86,7 +112,8 @@ void DocStringAnalyser::checkParameters(
auto paramRange = _annotation.docTags.equal_range("param");
for (auto i = paramRange.first; i != paramRange.second; ++i)
if (!validParams.count(i->second.paramName))
appendError(
m_errorReporter.docstringParsingError(
3881_error,
_node.documentation()->location(),
"Documented parameter \"" +
i->second.paramName +
@ -127,8 +154,7 @@ void DocStringAnalyser::parseDocStrings(
DocStringParser parser;
if (_node.documentation() && !_node.documentation()->text()->empty())
{
if (!parser.parse(*_node.documentation()->text(), m_errorReporter))
m_errorOccured = true;
parser.parse(*_node.documentation()->text(), m_errorReporter);
_annotation.docTags = parser.tags();
}
@ -136,7 +162,8 @@ void DocStringAnalyser::parseDocStrings(
for (auto const& docTag: _annotation.docTags)
{
if (!_validTags.count(docTag.first))
appendError(
m_errorReporter.docstringParsingError(
6546_error,
_node.documentation()->location(),
"Documentation tag @" + docTag.first + " not valid for " + _nodeName + "."
);
@ -144,13 +171,29 @@ void DocStringAnalyser::parseDocStrings(
if (docTag.first == "return")
{
returnTagsVisited++;
if (auto* function = dynamic_cast<FunctionDefinition const*>(&_node))
if (auto* varDecl = dynamic_cast<VariableDeclaration const*>(&_node))
{
if (!varDecl->isPublic())
m_errorReporter.docstringParsingError(
9440_error,
_node.documentation()->location(),
"Documentation tag \"@" + docTag.first + "\" is only allowed on public state-variables."
);
if (returnTagsVisited > 1)
m_errorReporter.docstringParsingError(
5256_error,
_node.documentation()->location(),
"Documentation tag \"@" + docTag.first + "\" is only allowed once on state-variables."
);
}
else if (auto* function = dynamic_cast<FunctionDefinition const*>(&_node))
{
string content = docTag.second.content;
string firstWord = content.substr(0, content.find_first_of(" \t"));
if (returnTagsVisited > function->returnParameters().size())
appendError(
m_errorReporter.docstringParsingError(
2604_error,
_node.documentation()->location(),
"Documentation tag \"@" + docTag.first + " " + docTag.second.content + "\"" +
" exceeds the number of return parameters."
@ -159,7 +202,8 @@ void DocStringAnalyser::parseDocStrings(
{
auto parameter = function->returnParameters().at(returnTagsVisited - 1);
if (!parameter->name().empty() && parameter->name() != firstWord)
appendError(
m_errorReporter.docstringParsingError(
5856_error,
_node.documentation()->location(),
"Documentation tag \"@" + docTag.first + " " + docTag.second.content + "\"" +
" does not contain the name of its return parameter."
@ -169,9 +213,3 @@ void DocStringAnalyser::parseDocStrings(
}
}
}
void DocStringAnalyser::appendError(SourceLocation const& _location, string const& _description)
{
m_errorOccured = true;
m_errorReporter.docstringParsingError(7816_error, _location, _description);
}

View File

@ -46,6 +46,7 @@ public:
private:
bool visit(ContractDefinition const& _contract) override;
bool visit(FunctionDefinition const& _function) override;
bool visit(VariableDeclaration const& _variable) override;
bool visit(ModifierDefinition const& _modifier) override;
bool visit(EventDefinition const& _event) override;
@ -67,6 +68,12 @@ private:
StructurallyDocumentedAnnotation& _annotation
);
void handleDeclaration(
Declaration const& _declaration,
StructurallyDocumented const& _node,
StructurallyDocumentedAnnotation& _annotation
);
void parseDocStrings(
StructurallyDocumented const& _node,
StructurallyDocumentedAnnotation& _annotation,
@ -74,9 +81,6 @@ private:
std::string const& _nodeName
);
void appendError(langutil::SourceLocation const& _location, std::string const& _description);
bool m_errorOccured = false;
langutil::ErrorReporter& m_errorReporter;
};

View File

@ -37,16 +37,13 @@ namespace solidity::frontend
NameAndTypeResolver::NameAndTypeResolver(
GlobalContext& _globalContext,
langutil::EVMVersion _evmVersion,
map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes,
ErrorReporter& _errorReporter
):
m_scopes(_scopes),
m_evmVersion(_evmVersion),
m_errorReporter(_errorReporter),
m_globalContext(_globalContext)
{
if (!m_scopes[nullptr])
m_scopes[nullptr] = make_shared<DeclarationContainer>();
m_scopes[nullptr] = make_shared<DeclarationContainer>();
for (Declaration const* declaration: _globalContext.declarations())
{
solAssert(m_scopes[nullptr]->registerDeclaration(*declaration), "Unable to register global declaration.");

View File

@ -56,7 +56,6 @@ public:
NameAndTypeResolver(
GlobalContext& _globalContext,
langutil::EVMVersion _evmVersion,
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes,
langutil::ErrorReporter& _errorReporter
);
/// Registers all declarations found in the AST node, usually a source unit.
@ -123,7 +122,7 @@ private:
/// where nullptr denotes the global scope. Note that structs are not scope since they do
/// not contain code.
/// Aliases (for example `import "x" as y;`) create multiple pointers to the same scope.
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& m_scopes;
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>> m_scopes;
langutil::EVMVersion m_evmVersion;
DeclarationContainer* m_currentScope = nullptr;

View File

@ -311,8 +311,8 @@ Token OverrideProxy::functionKind() const
FunctionType const* OverrideProxy::functionType() const
{
return std::visit(GenericVisitor{
[&](FunctionDefinition const* _item) { return FunctionType(*_item).asCallableFunction(false); },
[&](VariableDeclaration const* _item) { return FunctionType(*_item).asCallableFunction(false); },
[&](FunctionDefinition const* _item) { return FunctionType(*_item).asExternallyCallableFunction(false); },
[&](VariableDeclaration const* _item) { return FunctionType(*_item).asExternallyCallableFunction(false); },
[&](ModifierDefinition const*) -> FunctionType const* { solAssert(false, "Requested function type of modifier."); return nullptr; }
}, m_item);
}
@ -481,7 +481,12 @@ void OverrideChecker::checkIllegalOverrides(ContractDefinition const& _contract)
for (auto const* stateVar: _contract.stateVariables())
{
if (!stateVar->isPublic())
{
if (stateVar->overrides())
m_errorReporter.typeError(8022_error, stateVar->location(), "Override can only be used with public state variables.");
continue;
}
if (contains_if(inheritedMods, MatchByName{stateVar->name()}))
m_errorReporter.typeError(1456_error, stateVar->location(), "Override changes modifier to public state variable.");
@ -559,7 +564,7 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
overrideError(
_overriding,
_super,
2837_error,
6959_error,
"Overriding function changes state mutability from \"" +
stateMutabilityToString(_super.stateMutability()) +
"\" to \"" +

View File

@ -44,8 +44,9 @@ namespace solidity::frontend
bool ReferencesResolver::resolve(ASTNode const& _root)
{
auto errorWatcher = m_errorReporter.errorWatcher();
_root.accept(*this);
return !m_errorOccurred;
return errorWatcher.ok();
}
bool ReferencesResolver::visit(Block const& _block)
@ -118,7 +119,7 @@ bool ReferencesResolver::visit(Identifier const& _identifier)
else
errorMessage += " Did you mean " + std::move(suggestions) + "?";
}
declarationError(_identifier.location(), errorMessage);
m_errorReporter.declarationError(7576_error, _identifier.location(), errorMessage);
}
else if (declarations.size() == 1)
_identifier.annotation().referencedDeclaration = declarations.front();
@ -156,7 +157,7 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName)
Declaration const* declaration = m_resolver.pathFromCurrentScope(_typeName.namePath());
if (!declaration)
{
fatalDeclarationError(_typeName.location(), "Identifier not found or not unique.");
m_errorReporter.fatalDeclarationError(7920_error, _typeName.location(), "Identifier not found or not unique.");
return;
}
@ -208,14 +209,22 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
));
if (realName.empty())
{
declarationError(_identifier.location, "In variable names _slot and _offset can only be used as a suffix.");
m_errorReporter.declarationError(
4794_error,
_identifier.location,
"In variable names _slot and _offset can only be used as a suffix."
);
return;
}
declarations = m_resolver.nameFromCurrentScope(realName);
}
if (declarations.size() > 1)
{
declarationError(_identifier.location, "Multiple matching identifiers. Resolving overloaded identifiers is not supported.");
m_errorReporter.declarationError(
4718_error,
_identifier.location,
"Multiple matching identifiers. Resolving overloaded identifiers is not supported."
);
return;
}
else if (declarations.size() == 0)
@ -223,7 +232,11 @@ void ReferencesResolver::operator()(yul::Identifier const& _identifier)
if (auto var = dynamic_cast<VariableDeclaration const*>(declarations.front()))
if (var->isLocalVariable() && m_yulInsideFunction)
{
declarationError(_identifier.location, "Cannot access local Solidity variables from inside an inline assembly function.");
m_errorReporter.declarationError(
6578_error,
_identifier.location,
"Cannot access local Solidity variables from inside an inline assembly function."
);
return;
}
@ -241,7 +254,11 @@ void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl)
string namePrefix = identifier.name.str().substr(0, identifier.name.str().find('.'));
if (isSlot || isOffset)
declarationError(identifier.location, "In variable declarations _slot and _offset can not be used as a suffix.");
m_errorReporter.declarationError(
9155_error,
identifier.location,
"In variable declarations _slot and _offset can not be used as a suffix."
);
else if (
auto declarations = m_resolver.nameFromCurrentScope(namePrefix);
!declarations.empty()
@ -251,7 +268,8 @@ void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl)
for (auto const* decl: declarations)
ssl.append("The shadowed declaration is here:", decl->location());
if (!ssl.infos.empty())
declarationError(
m_errorReporter.declarationError(
3859_error,
identifier.location,
ssl,
namePrefix.size() < identifier.name.str().size() ?
@ -265,22 +283,4 @@ void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl)
visit(*_varDecl.value);
}
void ReferencesResolver::declarationError(SourceLocation const& _location, string const& _description)
{
m_errorOccurred = true;
m_errorReporter.declarationError(8532_error, _location, _description);
}
void ReferencesResolver::declarationError(SourceLocation const& _location, SecondarySourceLocation const& _ssl, string const& _description)
{
m_errorOccurred = true;
m_errorReporter.declarationError(3881_error, _location, _ssl, _description);
}
void ReferencesResolver::fatalDeclarationError(SourceLocation const& _location, string const& _description)
{
m_errorOccurred = true;
m_errorReporter.fatalDeclarationError(6546_error, _location, _description);
}
}

View File

@ -88,22 +88,12 @@ private:
void operator()(yul::Identifier const& _identifier) override;
void operator()(yul::VariableDeclaration const& _varDecl) override;
/// Adds a new error to the list of errors.
void declarationError(langutil::SourceLocation const& _location, std::string const& _description);
/// Adds a new error to the list of errors.
void declarationError(langutil::SourceLocation const& _location, langutil::SecondarySourceLocation const& _ssl, std::string const& _description);
/// Adds a new error to the list of errors and throws to abort reference resolving.
void fatalDeclarationError(langutil::SourceLocation const& _location, std::string const& _description);
langutil::ErrorReporter& m_errorReporter;
NameAndTypeResolver& m_resolver;
langutil::EVMVersion m_evmVersion;
/// Stack of return parameters.
std::vector<ParameterList const*> m_returnParameters;
bool const m_resolveInsideCode;
bool m_errorOccurred = false;
InlineAssemblyAnnotation* m_yulAnnotation = nullptr;
bool m_yulInsideFunction = false;

View File

@ -273,6 +273,7 @@ bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly)
"The msize instruction cannot be used when the Yul optimizer is activated because "
"it can change its semantics. Either disable the Yul optimizer or do not use the instruction."
);
return false;
}

View File

@ -193,6 +193,18 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c
typeArgument->location(),
"Decoding type " + actualType->toString(false) + " not supported."
);
if (auto referenceType = dynamic_cast<ReferenceType const*>(actualType))
{
auto result = referenceType->validForLocation(referenceType->location());
if (!result)
m_errorReporter.typeError(
6118_error,
typeArgument->location(),
result.message()
);
}
components.push_back(actualType);
}
else
@ -453,7 +465,15 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
if (contractType->contractDefinition().isLibrary())
m_errorReporter.typeError(1273_error, _variable.location(), "The type of a variable cannot be a library.");
if (_variable.value())
expectType(*_variable.value(), *varType);
{
if (_variable.isStateVariable() && dynamic_cast<MappingType const*>(varType))
{
m_errorReporter.typeError(6280_error, _variable.location(), "Mappings cannot be assigned to.");
_variable.value()->accept(*this);
}
else
expectType(*_variable.value(), *varType);
}
if (_variable.isConstant())
{
if (!_variable.type()->isValueType())
@ -671,14 +691,6 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
m_errorReporter.typeError(3224_error, _identifier.location, "Constant has no value.");
return size_t(-1);
}
else if (!var || !type(*var)->isValueType() || (
dynamic_cast<Literal const*>(var->value().get()) == nullptr &&
type(*var->value())->category() != Type::Category::RationalNumber
))
{
m_errorReporter.typeError(7615_error, _identifier.location, "Only direct number constants and references to such constants are supported by inline assembly.");
return size_t(-1);
}
else if (_context == yul::IdentifierContext::LValue)
{
m_errorReporter.typeError(6252_error, _identifier.location, "Constant variables cannot be assigned to.");
@ -689,8 +701,27 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
m_errorReporter.typeError(6617_error, _identifier.location, "The suffixes _offset and _slot can only be used on non-constant storage variables.");
return size_t(-1);
}
else if (var && var->value() && !var->value()->annotation().type && !dynamic_cast<Literal const*>(var->value().get()))
{
m_errorReporter.typeError(
2249_error,
_identifier.location,
"Constant variables with non-literal values cannot be forward referenced from inline assembly."
);
return size_t(-1);
}
else if (!var || !type(*var)->isValueType() || (
!dynamic_cast<Literal const*>(var->value().get()) &&
type(*var->value())->category() != Type::Category::RationalNumber
))
{
m_errorReporter.typeError(7615_error, _identifier.location, "Only direct number constants and references to such constants are supported by inline assembly.");
return size_t(-1);
}
}
solAssert(!dynamic_cast<FixedPointType const*>(var->type()), "FixedPointType not implemented.");
if (requiresStorage)
{
if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage))
@ -1501,6 +1532,12 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
{
if (!inlineArrayType)
m_errorReporter.fatalTypeError(6378_error, _tuple.location(), "Unable to deduce common type for array elements.");
else if (!inlineArrayType->nameable())
m_errorReporter.fatalTypeError(
9656_error,
_tuple.location(),
"Unable to deduce nameable type for array elements. Try adding explicit type conversion for the first element."
);
else if (!inlineArrayType->canLiveOutsideStorage())
m_errorReporter.fatalTypeError(1545_error, _tuple.location(), "Type " + inlineArrayType->toString() + " is only valid in storage.");
@ -1986,19 +2023,16 @@ void TypeChecker::typeCheckFunctionGeneralChecks(
bool const isStructConstructorCall =
_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall;
string msg;
if (isVariadic)
msg +=
auto [errorId, description] = [&]() -> tuple<ErrorId, string> {
string msg = isVariadic ?
"Need at least " +
toString(parameterTypes.size()) +
" arguments for " +
string(isStructConstructorCall ? "struct constructor" : "function call") +
", but provided only " +
toString(arguments.size()) +
".";
else
msg +=
"."
:
"Wrong argument count for " +
string(isStructConstructorCall ? "struct constructor" : "function call") +
": " +
@ -2008,49 +2042,64 @@ void TypeChecker::typeCheckFunctionGeneralChecks(
toString(parameterTypes.size()) +
".";
// Extend error message in case we try to construct a struct with mapping member.
if (isStructConstructorCall)
{
/// For error message: Struct members that were removed during conversion to memory.
TypePointer const expressionType = type(_functionCall.expression());
TypeType const& t = dynamic_cast<TypeType const&>(*expressionType);
auto const& structType = dynamic_cast<StructType const&>(*t.actualType());
set<string> membersRemovedForStructConstructor = structType.membersMissingInMemory();
if (!membersRemovedForStructConstructor.empty())
// Extend error message in case we try to construct a struct with mapping member.
if (isStructConstructorCall)
{
msg += " Members that have to be skipped in memory:";
for (auto const& member: membersRemovedForStructConstructor)
msg += " " + member;
/// For error message: Struct members that were removed during conversion to memory.
TypePointer const expressionType = type(_functionCall.expression());
auto const& t = dynamic_cast<TypeType const&>(*expressionType);
auto const& structType = dynamic_cast<StructType const&>(*t.actualType());
set<string> membersRemovedForStructConstructor = structType.membersMissingInMemory();
if (!membersRemovedForStructConstructor.empty())
{
msg += " Members that have to be skipped in memory:";
for (auto const& member: membersRemovedForStructConstructor)
msg += " " + member;
}
return { isVariadic ? 1123_error : 9755_error, msg };
}
}
else if (
_functionType->kind() == FunctionType::Kind::BareCall ||
_functionType->kind() == FunctionType::Kind::BareCallCode ||
_functionType->kind() == FunctionType::Kind::BareDelegateCall ||
_functionType->kind() == FunctionType::Kind::BareStaticCall
)
{
if (arguments.empty())
msg +=
else if (
_functionType->kind() == FunctionType::Kind::BareCall ||
_functionType->kind() == FunctionType::Kind::BareCallCode ||
_functionType->kind() == FunctionType::Kind::BareDelegateCall ||
_functionType->kind() == FunctionType::Kind::BareStaticCall
)
{
if (arguments.empty())
return {
isVariadic ? 7653_error : 6138_error,
msg +
" This function requires a single bytes argument."
" Use \"\" as argument to provide empty calldata."
};
else
return {
isVariadic ? 9390_error : 8922_error,
msg +
" This function requires a single bytes argument."
" If all your arguments are value types, you can use"
" abi.encode(...) to properly generate it."
};
}
else if (
_functionType->kind() == FunctionType::Kind::KECCAK256 ||
_functionType->kind() == FunctionType::Kind::SHA256 ||
_functionType->kind() == FunctionType::Kind::RIPEMD160
)
return {
isVariadic ? 1220_error : 4323_error,
msg +
" This function requires a single bytes argument."
" Use \"\" as argument to provide empty calldata.";
" Use abi.encodePacked(...) to obtain the pre-0.5.0"
" behaviour or abi.encode(...) to use ABI encoding."
};
else
msg +=
" This function requires a single bytes argument."
" If all your arguments are value types, you can use"
" abi.encode(...) to properly generate it.";
}
else if (
_functionType->kind() == FunctionType::Kind::KECCAK256 ||
_functionType->kind() == FunctionType::Kind::SHA256 ||
_functionType->kind() == FunctionType::Kind::RIPEMD160
)
msg +=
" This function requires a single bytes argument."
" Use abi.encodePacked(...) to obtain the pre-0.5.0"
" behaviour or abi.encode(...) to use ABI encoding.";
m_errorReporter.typeError(1093_error, _functionCall.location(), msg);
return { isVariadic ? 9308_error : 6160_error, msg };
}();
m_errorReporter.typeError(errorId, _functionCall.location(), description);
return;
}
@ -2126,33 +2175,43 @@ void TypeChecker::typeCheckFunctionGeneralChecks(
solAssert(!!paramArgMap[i], "unmapped parameter");
if (!type(*paramArgMap[i])->isImplicitlyConvertibleTo(*parameterTypes[i]))
{
string msg =
"Invalid type for argument in function call. "
"Invalid implicit conversion from " +
type(*paramArgMap[i])->toString() +
" to " +
parameterTypes[i]->toString() +
" requested.";
if (
_functionType->kind() == FunctionType::Kind::BareCall ||
_functionType->kind() == FunctionType::Kind::BareCallCode ||
_functionType->kind() == FunctionType::Kind::BareDelegateCall ||
_functionType->kind() == FunctionType::Kind::BareStaticCall
)
msg +=
" This function requires a single bytes argument."
" If all your arguments are value types, you can"
" use abi.encode(...) to properly generate it.";
else if (
_functionType->kind() == FunctionType::Kind::KECCAK256 ||
_functionType->kind() == FunctionType::Kind::SHA256 ||
_functionType->kind() == FunctionType::Kind::RIPEMD160
)
msg +=
" This function requires a single bytes argument."
" Use abi.encodePacked(...) to obtain the pre-0.5.0"
" behaviour or abi.encode(...) to use ABI encoding.";
m_errorReporter.typeError(6706_error, paramArgMap[i]->location(), msg);
auto [errorId, description] = [&]() -> tuple<ErrorId, string> {
string msg =
"Invalid type for argument in function call. "
"Invalid implicit conversion from " +
type(*paramArgMap[i])->toString() +
" to " +
parameterTypes[i]->toString() +
" requested.";
if (
_functionType->kind() == FunctionType::Kind::BareCall ||
_functionType->kind() == FunctionType::Kind::BareCallCode ||
_functionType->kind() == FunctionType::Kind::BareDelegateCall ||
_functionType->kind() == FunctionType::Kind::BareStaticCall
)
return {
8051_error,
msg +
" This function requires a single bytes argument."
" If all your arguments are value types, you can"
" use abi.encode(...) to properly generate it."
};
else if (
_functionType->kind() == FunctionType::Kind::KECCAK256 ||
_functionType->kind() == FunctionType::Kind::SHA256 ||
_functionType->kind() == FunctionType::Kind::RIPEMD160
)
return {
7556_error,
msg +
" This function requires a single bytes argument."
" Use abi.encodePacked(...) to obtain the pre-0.5.0"
" behaviour or abi.encode(...) to use ABI encoding."
};
else
return { 9553_error, msg };
}();
m_errorReporter.typeError(errorId, paramArgMap[i]->location(), description);
}
}
}
@ -2525,7 +2584,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
if (possibleMembers.empty())
{
if (initialMemberCount == 0)
if (initialMemberCount == 0 && !dynamic_cast<ArraySliceType const*>(exprType))
{
// Try to see if the member was removed because it is only available for storage types.
auto storageType = TypeProvider::withLocationIfReference(
@ -2541,61 +2600,70 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
" outside of storage."
);
}
string errorMsg = "Member \"" + memberName + "\" not found or not visible "
auto [errorId, description] = [&]() -> tuple<ErrorId, string> {
string errorMsg = "Member \"" + memberName + "\" not found or not visible "
"after argument-dependent lookup in " + exprType->toString() + ".";
if (auto const& funType = dynamic_cast<FunctionType const*>(exprType))
{
auto const& t = funType->returnParameterTypes();
if (memberName == "value")
if (auto const* funType = dynamic_cast<FunctionType const*>(exprType))
{
if (funType->kind() == FunctionType::Kind::Creation)
errorMsg = "Constructor for " + t.front()->toString() + " must be payable for member \"value\" to be available.";
else if (
funType->kind() == FunctionType::Kind::DelegateCall ||
funType->kind() == FunctionType::Kind::BareDelegateCall
)
errorMsg = "Member \"value\" is not allowed in delegated calls due to \"msg.value\" persisting.";
else
errorMsg = "Member \"value\" is only available for payable functions.";
}
else if (
t.size() == 1 &&
(t.front()->category() == Type::Category::Struct ||
t.front()->category() == Type::Category::Contract)
)
errorMsg += " Did you intend to call the function?";
}
else if (exprType->category() == Type::Category::Contract)
{
for (auto const& addressMember: TypeProvider::payableAddress()->nativeMembers(nullptr))
if (addressMember.name == memberName)
TypePointers const& t = funType->returnParameterTypes();
if (memberName == "value")
{
Identifier const* var = dynamic_cast<Identifier const*>(&_memberAccess.expression());
string varName = var ? var->name() : "...";
errorMsg += " Use \"address(" + varName + ")." + memberName + "\" to access this address member.";
break;
if (funType->kind() == FunctionType::Kind::Creation)
return {
8827_error,
"Constructor for " + t.front()->toString() + " must be payable for member \"value\" to be available."
};
else if (
funType->kind() == FunctionType::Kind::DelegateCall ||
funType->kind() == FunctionType::Kind::BareDelegateCall
)
return { 8477_error, "Member \"value\" is not allowed in delegated calls due to \"msg.value\" persisting." };
else
return { 8820_error, "Member \"value\" is only available for payable functions." };
}
}
else if (auto addressType = dynamic_cast<AddressType const*>(exprType))
{
// Trigger error when using send or transfer with a non-payable fallback function.
if (memberName == "send" || memberName == "transfer")
{
solAssert(
addressType->stateMutability() != StateMutability::Payable,
"Expected address not-payable as members were not found"
);
errorMsg = "\"send\" and \"transfer\" are only available for objects of type \"address payable\", not \"" + exprType->toString() + "\".";
else if (
t.size() == 1 && (
t.front()->category() == Type::Category::Struct ||
t.front()->category() == Type::Category::Contract
)
)
return { 6005_error, errorMsg + " Did you intend to call the function?" };
}
}
else if (exprType->category() == Type::Category::Contract)
{
for (MemberList::Member const& addressMember: TypeProvider::payableAddress()->nativeMembers(nullptr))
if (addressMember.name == memberName)
{
auto const* var = dynamic_cast<Identifier const*>(&_memberAccess.expression());
string varName = var ? var->name() : "...";
errorMsg += " Use \"address(" + varName + ")." + memberName + "\" to access this address member.";
return { 3125_error, errorMsg };
}
}
else if (auto const* addressType = dynamic_cast<AddressType const*>(exprType))
{
// Trigger error when using send or transfer with a non-payable fallback function.
if (memberName == "send" || memberName == "transfer")
{
solAssert(
addressType->stateMutability() != StateMutability::Payable,
"Expected address not-payable as members were not found"
);
return { 9862_error, "\"send\" and \"transfer\" are only available for objects of type \"address payable\", not \"" + exprType->toString() + "\"." };
}
}
return { 9582_error, errorMsg };
}();
m_errorReporter.fatalTypeError(
4035_error,
errorId,
_memberAccess.location(),
errorMsg
description
);
}
else if (possibleMembers.size() > 1)
@ -2865,7 +2933,7 @@ bool TypeChecker::visit(IndexRangeAccess const& _access)
if (arrayType->location() != DataLocation::CallData || !arrayType->isDynamicallySized())
m_errorReporter.typeError(1227_error, _access.location(), "Index range access is only supported for dynamic calldata arrays.");
else if (arrayType->baseType()->isDynamicallyEncoded())
m_errorReporter.typeError(1878_error, _access.location(), "Index range access is not supported for arrays with dynamically encoded base types.");
m_errorReporter.typeError(2148_error, _access.location(), "Index range access is not supported for arrays with dynamically encoded base types.");
_access.annotation().type = TypeProvider::arraySlice(*arrayType);
_access.annotation().isLValue = isLValue;
_access.annotation().isPure = isPure;
@ -2924,7 +2992,11 @@ bool TypeChecker::visit(Identifier const& _identifier)
if (!annotation.referencedDeclaration)
{
annotation.overloadedDeclarations = cleanOverloadedDeclarations(_identifier, annotation.candidateDeclarations);
if (!annotation.arguments)
if (annotation.overloadedDeclarations.empty())
m_errorReporter.fatalTypeError(7593_error, _identifier.location(), "No candidates for overload resolution found.");
else if (annotation.overloadedDeclarations.size() == 1)
annotation.referencedDeclaration = *annotation.overloadedDeclarations.begin();
else if (!annotation.arguments)
{
// The identifier should be a public state variable shadowing other functions
vector<Declaration const*> candidates;
@ -2941,10 +3013,6 @@ bool TypeChecker::visit(Identifier const& _identifier)
else
m_errorReporter.fatalTypeError(7589_error, _identifier.location(), "No unique declaration found after variable lookup.");
}
else if (annotation.overloadedDeclarations.empty())
m_errorReporter.fatalTypeError(7593_error, _identifier.location(), "No candidates for overload resolution found.");
else if (annotation.overloadedDeclarations.size() == 1)
annotation.referencedDeclaration = *annotation.overloadedDeclarations.begin();
else
{
vector<Declaration const*> candidates;
@ -3157,17 +3225,17 @@ void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAss
if (_expression.annotation().isLValue)
return;
return m_errorReporter.typeError(1123_error, _expression.location(), [&]() {
auto [errorId, description] = [&]() -> tuple<ErrorId, string> {
if (_expression.annotation().isConstant)
return "Cannot assign to a constant variable.";
return { 6520_error, "Cannot assign to a constant variable." };
if (auto indexAccess = dynamic_cast<IndexAccess const*>(&_expression))
{
if (type(indexAccess->baseExpression())->category() == Type::Category::FixedBytes)
return "Single bytes in fixed bytes arrays cannot be modified.";
return { 4360_error, "Single bytes in fixed bytes arrays cannot be modified." };
else if (auto arrayType = dynamic_cast<ArrayType const*>(type(indexAccess->baseExpression())))
if (arrayType->dataStoredIn(DataLocation::CallData))
return "Calldata arrays are read-only.";
return { 6182_error, "Calldata arrays are read-only." };
}
if (auto memberAccess = dynamic_cast<MemberAccess const*>(&_expression))
@ -3175,18 +3243,20 @@ void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAss
if (auto structType = dynamic_cast<StructType const*>(type(memberAccess->expression())))
{
if (structType->dataStoredIn(DataLocation::CallData))
return "Calldata structs are read-only.";
return { 4156_error, "Calldata structs are read-only." };
}
else if (dynamic_cast<ArrayType const*>(type(memberAccess->expression())))
if (memberAccess->memberName() == "length")
return "Member \"length\" is read-only and cannot be used to resize arrays.";
return { 7567_error, "Member \"length\" is read-only and cannot be used to resize arrays." };
}
if (auto identifier = dynamic_cast<Identifier const*>(&_expression))
if (auto varDecl = dynamic_cast<VariableDeclaration const*>(identifier->annotation().referencedDeclaration))
if (varDecl->isExternalCallableParameter() && dynamic_cast<ReferenceType const*>(identifier->annotation().type))
return "External function arguments of reference type are read-only.";
return { 7128_error, "External function arguments of reference type are read-only." };
return "Expression has to be an lvalue.";
}());
return { 4247_error, "Expression has to be an lvalue." };
}();
m_errorReporter.typeError(errorId, _expression.location(), description);
}

View File

@ -153,10 +153,10 @@ FunctionDefinition const* ContractDefinition::receiveFunction() const
vector<EventDefinition const*> const& ContractDefinition::interfaceEvents() const
{
if (!m_interfaceEvents)
{
return m_interfaceEvents.init([&]{
set<string> eventsSeen;
m_interfaceEvents = make_unique<vector<EventDefinition const*>>();
vector<EventDefinition const*> interfaceEvents;
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
for (EventDefinition const* e: contract->events())
{
@ -169,19 +169,20 @@ vector<EventDefinition const*> const& ContractDefinition::interfaceEvents() cons
if (eventsSeen.count(eventSignature) == 0)
{
eventsSeen.insert(eventSignature);
m_interfaceEvents->push_back(e);
interfaceEvents.push_back(e);
}
}
}
return *m_interfaceEvents;
return interfaceEvents;
});
}
vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList(bool _includeInheritedFunctions) const
{
if (!m_interfaceFunctionList[_includeInheritedFunctions])
{
return m_interfaceFunctionList[_includeInheritedFunctions].init([&]{
set<string> signaturesSeen;
m_interfaceFunctionList[_includeInheritedFunctions] = make_unique<vector<pair<util::FixedHash<4>, FunctionTypePointer>>>();
vector<pair<util::FixedHash<4>, FunctionTypePointer>> interfaceFunctionList;
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
{
if (_includeInheritedFunctions == false && contract != this)
@ -203,12 +204,13 @@ vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition:
{
signaturesSeen.insert(functionSignature);
util::FixedHash<4> hash(util::keccak256(functionSignature));
m_interfaceFunctionList[_includeInheritedFunctions]->emplace_back(hash, fun);
interfaceFunctionList.emplace_back(hash, fun);
}
}
}
}
return *m_interfaceFunctionList[_includeInheritedFunctions];
return interfaceFunctionList;
});
}
TypePointer ContractDefinition::type() const
@ -336,8 +338,14 @@ TypePointer FunctionDefinition::type() const
TypePointer FunctionDefinition::typeViaContractName() const
{
if (annotation().contract->isLibrary())
return FunctionType(*this).asCallableFunction(true);
return TypeProvider::function(*this, FunctionType::Kind::Declaration);
{
if (isPublic())
return FunctionType(*this).asExternallyCallableFunction(true);
else
return TypeProvider::function(*this, FunctionType::Kind::Internal);
}
else
return TypeProvider::function(*this, FunctionType::Kind::Declaration);
}
string FunctionDefinition::externalSignature() const
@ -367,7 +375,7 @@ FunctionDefinition const& FunctionDefinition::resolveVirtual(
solAssert(!dynamic_cast<ContractDefinition const&>(*scope()).isLibrary(), "");
FunctionType const* functionType = TypeProvider::function(*this)->asCallableFunction(false);
FunctionType const* functionType = TypeProvider::function(*this)->asExternallyCallableFunction(false);
for (ContractDefinition const* c: _mostDerivedContract.annotation().linearizedBaseContracts)
{
@ -378,7 +386,7 @@ FunctionDefinition const& FunctionDefinition::resolveVirtual(
if (
function->name() == name() &&
!function->isConstructor() &&
FunctionType(*function).asCallableFunction(false)->hasEqualParameterTypes(*functionType)
FunctionType(*function).asExternallyCallableFunction(false)->hasEqualParameterTypes(*functionType)
)
return *function;
}
@ -614,18 +622,14 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c
if (!hasReferenceOrMappingType() || isStateVariable() || isEventParameter())
return set<Location>{ Location::Unspecified };
else if (isExternalCallableParameter())
{
set<Location> locations{ Location::CallData };
if (isLibraryFunctionParameter())
locations.insert(Location::Storage);
return locations;
}
else if (isCallableOrCatchParameter())
{
set<Location> locations{ Location::Memory };
if (isInternalCallableParameter() || isLibraryFunctionParameter() || isTryCatchParameter())
locations.insert(Location::Storage);
if (!isTryCatchParameter())
locations.insert(Location::CallData);
return locations;
}
else if (isLocalVariable())
@ -640,8 +644,7 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c
case Type::Category::Mapping:
return set<Location>{ Location::Storage };
default:
// TODO: add Location::Calldata once implemented for local variables.
return set<Location>{ Location::Memory, Location::Storage };
return set<Location>{ Location::Memory, Location::Storage, Location::CallData };
}
};
return dataLocations(typeName()->annotation().type, dataLocations);

View File

@ -31,6 +31,7 @@
#include <liblangutil/SourceLocation.h>
#include <libevmasm/Instruction.h>
#include <libsolutil/FixedHash.h>
#include <libsolutil/LazyInit.h>
#include <boost/noncopyable.hpp>
#include <json/json.h>
@ -536,8 +537,8 @@ private:
ContractKind m_contractKind;
bool m_abstract{false};
mutable std::unique_ptr<std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList[2];
mutable std::unique_ptr<std::vector<EventDefinition const*>> m_interfaceEvents;
util::LazyInit<std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList[2];
util::LazyInit<std::vector<EventDefinition const*>> m_interfaceEvents;
};
class InheritanceSpecifier: public ASTNode
@ -858,7 +859,7 @@ private:
* Declaration of a variable. This can be used in various places, e.g. in function parameter
* lists, struct definitions and even function bodies.
*/
class VariableDeclaration: public Declaration
class VariableDeclaration: public Declaration, public StructurallyDocumented
{
public:
enum Location { Unspecified, Storage, Memory, CallData };
@ -881,6 +882,7 @@ public:
ASTPointer<ASTString> const& _name,
ASTPointer<Expression> _value,
Visibility _visibility,
ASTPointer<StructuredDocumentation> const _documentation = nullptr,
bool _isStateVar = false,
bool _isIndexed = false,
Mutability _mutability = Mutability::Mutable,
@ -888,6 +890,7 @@ public:
Location _referenceLocation = Location::Unspecified
):
Declaration(_id, _location, _name, _visibility),
StructurallyDocumented(std::move(_documentation)),
m_typeName(std::move(_type)),
m_value(std::move(_value)),
m_isStateVariable(_isStateVar),

View File

@ -171,7 +171,7 @@ struct ModifierDefinitionAnnotation: CallableDeclarationAnnotation, Structurally
{
};
struct VariableDeclarationAnnotation: DeclarationAnnotation
struct VariableDeclarationAnnotation: DeclarationAnnotation, StructurallyDocumentedAnnotation
{
/// Type of variable (type of identifier referencing this variable).
TypePointer type = nullptr;
@ -250,7 +250,7 @@ struct ExpressionAnnotation: ASTAnnotation
bool lValueOfOrdinaryAssignment = false;
/// Types and - if given - names of arguments if the expr. is a function
/// that is called, used for overload resoultion
/// that is called, used for overload resolution
std::optional<FuncCallArguments> arguments;
};

View File

@ -391,6 +391,8 @@ bool ASTJsonConverter::visit(VariableDeclaration const& _node)
};
if (_node.isStateVariable() && _node.isPublic())
attributes.emplace_back("functionSelector", _node.externalIdentifierHex());
if (_node.isStateVariable() && _node.documentation())
attributes.emplace_back("documentation", toJson(*_node.documentation()));
if (m_inEvent)
attributes.emplace_back("indexed", _node.isIndexed());
if (!_node.annotation().baseFunctions.empty())

View File

@ -441,6 +441,7 @@ ASTPointer<VariableDeclaration> ASTJsonImporter::createVariableDeclaration(Json:
make_shared<ASTString>(member(_node, "name").asString()),
nullOrCast<Expression>(member(_node, "value")),
visibility(_node),
_node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")),
memberAsBool(_node, "stateVariable"),
_node.isMember("indexed") ? memberAsBool(_node, "indexed") : false,
mutability,

View File

@ -172,8 +172,7 @@ void StorageOffsets::computeOffsets(TypePointers const& _types)
++slotOffset;
byteOffset = 0;
}
if (slotOffset >= bigint(1) << 256)
BOOST_THROW_EXCEPTION(Error(Error::Type::TypeError) << util::errinfo_comment("Object too large for storage."));
solAssert(slotOffset < bigint(1) << 256 ,"Object too large for storage.");
offsets[i] = make_pair(u256(slotOffset), byteOffset);
solAssert(type->storageSize() >= 1, "Invalid storage size.");
if (type->storageSize() == 1 && byteOffset + type->storageBytes() <= 32)
@ -186,8 +185,7 @@ void StorageOffsets::computeOffsets(TypePointers const& _types)
}
if (byteOffset > 0)
++slotOffset;
if (slotOffset >= bigint(1) << 256)
BOOST_THROW_EXCEPTION(Error(Error::Type::TypeError) << util::errinfo_comment("Object too large for storage."));
solAssert(slotOffset < bigint(1) << 256, "Object too large for storage.");
m_storageSize = u256(slotOffset);
swap(m_offsets, offsets);
}
@ -207,26 +205,31 @@ void MemberList::combine(MemberList const & _other)
pair<u256, unsigned> const* MemberList::memberStorageOffset(string const& _name) const
{
if (!m_storageOffsets)
{
TypePointers memberTypes;
memberTypes.reserve(m_memberTypes.size());
for (auto const& member: m_memberTypes)
memberTypes.push_back(member.type);
m_storageOffsets = std::make_unique<StorageOffsets>();
m_storageOffsets->computeOffsets(memberTypes);
}
StorageOffsets const& offsets = storageOffsets();
for (size_t index = 0; index < m_memberTypes.size(); ++index)
if (m_memberTypes[index].name == _name)
return m_storageOffsets->offset(index);
return offsets.offset(index);
return nullptr;
}
u256 const& MemberList::storageSize() const
{
// trigger lazy computation
memberStorageOffset("");
return m_storageOffsets->storageSize();
return storageOffsets().storageSize();
}
StorageOffsets const& MemberList::storageOffsets() const {
return m_storageOffsets.init([&]{
TypePointers memberTypes;
memberTypes.reserve(m_memberTypes.size());
for (auto const& member: m_memberTypes)
memberTypes.push_back(member.type);
StorageOffsets storageOffsets;
storageOffsets.computeOffsets(memberTypes);
return storageOffsets;
});
}
/// Helper functions for type identifier
@ -344,14 +347,17 @@ TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool) c
MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition const& _scope)
{
// Normalise data location of type.
TypePointer type = TypeProvider::withLocationIfReference(DataLocation::Storage, &_type);
DataLocation typeLocation = DataLocation::Storage;
if (auto refType = dynamic_cast<ReferenceType const*>(&_type))
typeLocation = refType->location();
set<Declaration const*> seenFunctions;
MemberList::MemberMap members;
for (ContractDefinition const* contract: _scope.annotation().linearizedBaseContracts)
for (UsingForDirective const* ufd: contract->usingForDirectives())
{
if (ufd->typeName() && *type != *TypeProvider::withLocationIfReference(
DataLocation::Storage,
if (ufd->typeName() && _type != *TypeProvider::withLocationIfReference(
typeLocation,
ufd->typeName()->annotation().type
))
continue;
@ -365,7 +371,7 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition
seenFunctions.insert(function);
if (function->parameters().empty())
continue;
FunctionTypePointer fun = FunctionType(*function, FunctionType::Kind::External).asCallableFunction(true, true);
FunctionTypePointer fun = FunctionType(*function, FunctionType::Kind::External).asExternallyCallableFunction(true, true);
if (_type.isImplicitlyConvertibleTo(*fun->selfType()))
members.emplace_back(function->name(), fun, function);
}
@ -1768,8 +1774,7 @@ u256 ArrayType::storageSize() const
}
else
size = bigint(length()) * baseType()->storageSize();
if (size >= bigint(1) << 256)
BOOST_THROW_EXCEPTION(Error(Error::Type::TypeError) << util::errinfo_comment("Array too large for storage."));
solAssert(size < bigint(1) << 256, "Array too large for storage.");
return max<u256>(1, u256(size));
}
@ -2053,7 +2058,7 @@ MemberList::MemberMap ContractType::nativeMembers(ContractDefinition const* _con
for (auto const& it: m_contract.interfaceFunctions())
members.emplace_back(
it.second->declaration().name(),
it.second->asCallableFunction(m_contract.isLibrary()),
it.second->asExternallyCallableFunction(m_contract.isLibrary()),
&it.second->declaration()
);
}
@ -3021,6 +3026,17 @@ unsigned FunctionType::storageBytes() const
solAssert(false, "Storage size of non-storable function type requested.");
}
bool FunctionType::nameable() const
{
return
(m_kind == Kind::Internal || m_kind == Kind::External) &&
!m_bound &&
!m_arbitraryParameters &&
!m_gasSet &&
!m_valueSet &&
!m_saltSet;
}
vector<tuple<string, TypePointer>> FunctionType::makeStackItems() const
{
vector<tuple<string, TypePointer>> slots;
@ -3422,7 +3438,7 @@ TypePointer FunctionType::copyAndSetCallOptions(bool _setGas, bool _setValue, bo
);
}
FunctionTypePointer FunctionType::asCallableFunction(bool _inLibrary, bool _bound) const
FunctionTypePointer FunctionType::asExternallyCallableFunction(bool _inLibrary, bool _bound) const
{
if (_bound)
solAssert(!m_parameterTypes.empty(), "");

View File

@ -29,6 +29,7 @@
#include <libsolutil/Common.h>
#include <libsolutil/CommonIO.h>
#include <libsolutil/LazyInit.h>
#include <libsolutil/Result.h>
#include <boost/rational.hpp>
@ -139,8 +140,10 @@ public:
MemberMap::const_iterator end() const { return m_memberTypes.end(); }
private:
StorageOffsets const& storageOffsets() const;
MemberMap m_memberTypes;
mutable std::unique_ptr<StorageOffsets> m_storageOffsets;
util::LazyInit<StorageOffsets> m_storageOffsets;
};
static_assert(std::is_nothrow_move_constructible<MemberList>::value, "MemberList should be noexcept move constructible");
@ -260,6 +263,10 @@ public:
/// Returns true if the type can be stored as a value (as opposed to a reference) on the stack,
/// i.e. it behaves differently in lvalue context and in value context.
virtual bool isValueType() const { return false; }
/// @returns true if this type can be used for variables. It returns false for
/// types like magic types, literals and function types with a kind that is not
/// internal or external.
virtual bool nameable() const { return false; }
/// @returns a list of named and typed stack items that determine the layout of this type on the stack.
/// A stack item either has an empty name and type ``nullptr`` referring to a single stack slot, or
/// has a non-empty name and a valid type referring to the stack layout of that type.
@ -399,6 +406,7 @@ public:
unsigned storageBytes() const override { return 160 / 8; }
bool leftAligned() const override { return false; }
bool isValueType() const override { return true; }
bool nameable() const override { return true; }
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
@ -443,6 +451,7 @@ public:
unsigned storageBytes() const override { return m_bits / 8; }
bool leftAligned() const override { return false; }
bool isValueType() const override { return true; }
bool nameable() const override { return true; }
std::string toString(bool _short) const override;
@ -489,6 +498,7 @@ public:
unsigned storageBytes() const override { return m_totalBits / 8; }
bool leftAligned() const override { return false; }
bool isValueType() const override { return true; }
bool nameable() const override { return true; }
std::string toString(bool _short) const override;
@ -636,6 +646,7 @@ public:
unsigned storageBytes() const override { return m_bytes; }
bool leftAligned() const override { return true; }
bool isValueType() const override { return true; }
bool nameable() const override { return true; }
std::string toString(bool) const override { return "bytes" + util::toString(m_bytes); }
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
@ -663,6 +674,7 @@ public:
unsigned storageBytes() const override { return 1; }
bool leftAligned() const override { return false; }
bool isValueType() const override { return true; }
bool nameable() const override { return true; }
std::string toString(bool) const override { return "bool"; }
u256 literalValue(Literal const* _literal) const override;
@ -770,6 +782,7 @@ public:
bool isDynamicallyEncoded() const override;
u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); }
bool nameable() const override { return true; }
std::string toString(bool _short) const override;
std::string canonicalName() const override;
std::string signatureInExternalFunction(bool _structsByName) const override;
@ -872,6 +885,7 @@ public:
bool leftAligned() const override { solAssert(!isSuper(), ""); return false; }
bool canLiveOutsideStorage() const override { return !isSuper(); }
bool isValueType() const override { return !isSuper(); }
bool nameable() const override { return !isSuper(); }
std::string toString(bool _short) const override;
std::string canonicalName() const override;
@ -932,6 +946,7 @@ public:
u256 memoryDataSize() const override;
u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return true; }
bool nameable() const override { return true; }
std::string toString(bool _short) const override;
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
@ -994,6 +1009,7 @@ public:
std::string toString(bool _short) const override;
std::string canonicalName() const override;
bool isValueType() const override { return true; }
bool nameable() const override { return true; }
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
TypePointer encodingType() const override;
@ -1199,6 +1215,7 @@ public:
bool leftAligned() const override;
unsigned storageBytes() const override;
bool isValueType() const override { return true; }
bool nameable() const override;
bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
bool hasSimpleZeroValueInMemory() const override { return false; }
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
@ -1287,11 +1304,11 @@ public:
/// @returns a copy of this function type where the location of reference types is changed
/// from CallData to Memory. This is the type that would be used when the function is
/// called, as opposed to the parameter types that are available inside the function body.
/// called externally, as opposed to the parameter types that are available inside the function body.
/// Also supports variants to be used for library or bound calls.
/// @param _inLibrary if true, uses DelegateCall as location.
/// @param _bound if true, the function type is set to be bound.
FunctionTypePointer asCallableFunction(bool _inLibrary, bool _bound = false) const;
FunctionTypePointer asExternallyCallableFunction(bool _inLibrary, bool _bound = false) const;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
@ -1336,6 +1353,7 @@ public:
bool dataStoredIn(DataLocation _location) const override { return _location == DataLocation::Storage; }
/// Cannot be stored in memory, but just in case.
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
bool nameable() const override { return true; }
Type const* keyType() const { return m_keyType; }
Type const* valueType() const { return m_valueType; }

View File

@ -185,6 +185,13 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
{
solAssert(byteOffsetSize == 0, "Byte offset for array as base type.");
auto const& sourceBaseArrayType = dynamic_cast<ArrayType const&>(*sourceBaseType);
solUnimplementedAssert(
_sourceType.location() != DataLocation::CallData ||
!_sourceType.isDynamicallyEncoded() ||
!sourceBaseArrayType.isDynamicallySized(),
"Copying nested calldata dynamic arrays to storage is not implemented in the old code generator."
);
_context << Instruction::DUP3;
if (sourceBaseArrayType.location() == DataLocation::Memory)
_context << Instruction::MLOAD;

View File

@ -422,7 +422,12 @@ void CompilerContext::appendInlineAssembly(
ErrorReporter errorReporter(errors);
auto scanner = make_shared<langutil::Scanner>(langutil::CharStream(_assembly, "--CODEGEN--"));
yul::EVMDialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(m_evmVersion);
shared_ptr<yul::Block> parserResult = yul::Parser(errorReporter, dialect).parse(scanner, false);
optional<langutil::SourceLocation> locationOverride;
if (!_system)
locationOverride = m_asm->currentSourceLocation();
shared_ptr<yul::Block> parserResult =
yul::Parser(errorReporter, dialect, std::move(locationOverride))
.parse(scanner, false);
#ifdef SOL_OUTPUT_ASM
cout << yul::AsmPrinter(&dialect)(*parserResult) << endl;
#endif

View File

@ -974,6 +974,14 @@ void CompilerUtils::convertType(
}
else
{
if (auto baseType = dynamic_cast<ArrayType const*>(typeOnStack.baseType()))
solUnimplementedAssert(
typeOnStack.location() != DataLocation::CallData ||
!typeOnStack.isDynamicallyEncoded() ||
!baseType->isDynamicallySized(),
"Copying nested dynamic calldata arrays to memory is not implemented in the old code generator."
);
m_context << u256(0) << Instruction::SWAP1;
// stack: <mem start> <source ref> (variably sized) <length> <counter> <mem data pos>
auto repeat = m_context.newTag();
@ -1014,6 +1022,10 @@ void CompilerUtils::convertType(
case Type::Category::ArraySlice:
{
auto& typeOnStack = dynamic_cast<ArraySliceType const&>(_typeOnStack);
solUnimplementedAssert(
_targetType.dataStoredIn(DataLocation::CallData),
"Conversion from calldata slices to memory not yet implemented."
);
solAssert(_targetType == typeOnStack.arrayType(), "");
solUnimplementedAssert(
typeOnStack.arrayType().location() == DataLocation::CallData &&
@ -1210,6 +1222,15 @@ void CompilerUtils::pushZeroValue(Type const& _type)
m_context << u256(0);
return;
}
if (referenceType->location() == DataLocation::CallData)
{
solAssert(referenceType->sizeOnStack() == 1 || referenceType->sizeOnStack() == 2, "");
m_context << Instruction::CALLDATASIZE;
if (referenceType->sizeOnStack() == 2)
m_context << 0;
return;
}
solAssert(referenceType->location() == DataLocation::Memory, "");
if (auto arrayType = dynamic_cast<ArrayType const*>(&_type))
if (arrayType->isDynamicallySized())

View File

@ -286,7 +286,7 @@ public:
/// Appends code that computes the Keccak-256 hash of the topmost stack element of 32 byte type.
void computeHashStatic();
/// Apppends code that copies the code of the given contract to memory.
/// Appends code that copies the code of the given contract to memory.
/// Stack pre: Memory position
/// Stack post: Updated memory position
/// @param creation if true, copies creation code, if false copies runtime code.

View File

@ -1912,7 +1912,7 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
// If the identifier is called right away, this code is executed in visit(FunctionCall...), because
// we want to avoid having a reference to the runtime function entry point in the
// constructor context, since this would force the compiler to include unreferenced
// internal functions in the runtime contex.
// internal functions in the runtime context.
utils().pushCombinedFunctionEntryLabel(functionDef->resolveVirtual(m_context.mostDerivedContract()));
else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration))
appendVariable(*variable, static_cast<Expression const&>(_identifier));

View File

@ -602,6 +602,25 @@ string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
});
}
string YulUtilFunctions::extractByteArrayLengthFunction()
{
string functionName = "extract_byte_array_length";
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers w(R"(
function <functionName>(data) -> length {
// Retrieve length both for in-place strings and off-place strings:
// Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2
// i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it
// computes (x & (-1)) / 2, which is equivalent to just x / 2.
let mask := sub(mul(0x100, iszero(and(data, 1))), 1)
length := div(and(data, mask), 2)
}
)");
w("functionName", functionName);
return w.render();
});
}
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
{
string functionName = "array_length_" + _type.identifier();
@ -615,12 +634,7 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
<?storage>
length := sload(value)
<?byteArray>
// Retrieve length both for in-place strings and off-place strings:
// Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2
// i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it
// computes (x & (-1)) / 2, which is equivalent to just x / 2.
let mask := sub(mul(0x100, iszero(and(length, 1))), 1)
length := div(and(length, mask), 2)
length := <extractByteArrayLength>(length)
</byteArray>
</storage>
<!dynamic>
@ -634,7 +648,12 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
w("length", toCompactHexWithPrefix(_type.length()));
w("memory", _type.location() == DataLocation::Memory);
w("storage", _type.location() == DataLocation::Storage);
w("byteArray", _type.isByteArray());
if (_type.location() == DataLocation::Storage)
{
w("byteArray", _type.isByteArray());
if (_type.isByteArray())
w("extractByteArrayLength", extractByteArrayLengthFunction());
}
if (_type.isDynamicallySized())
solAssert(
_type.location() != DataLocation::CallData,
@ -689,8 +708,9 @@ string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
if (_type.isByteArray())
return storageByteArrayPopFunction(_type);
string functionName = "array_pop_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
@ -699,10 +719,8 @@ string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type)
let oldLen := <fetchLength>(array)
if iszero(oldLen) { invalid() }
let newLen := sub(oldLen, 1)
let slot, offset := <indexAccess>(array, newLen)
<setToZero>(slot, offset)
sstore(array, newLen)
})")
("functionName", functionName)
@ -713,29 +731,115 @@ string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type)
});
}
string YulUtilFunctions::storageByteArrayPopFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
solAssert(_type.isByteArray(), "");
string functionName = "byte_array_pop_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array) {
let data := sload(array)
let oldLen := <extractByteArrayLength>(data)
if iszero(oldLen) { invalid() }
switch eq(oldLen, 32)
case 1 {
// Here we have a special case where array transitions to shorter than 32
// So we need to copy data
let copyFromSlot := <dataAreaFunction>(array)
data := sload(copyFromSlot)
sstore(copyFromSlot, 0)
// New length is 31, encoded to 31 * 2 = 62
data := or(and(data, not(0xff)), 62)
}
default {
data := sub(data, 2)
let newLen := sub(oldLen, 1)
switch lt(oldLen, 32)
case 1 {
// set last element to zero
let mask := not(<shl>(mul(8, sub(31, newLen)), 0xff))
data := and(data, mask)
}
default {
let slot, offset := <indexAccess>(array, newLen)
<setToZero>(slot, offset)
}
}
sstore(array, data)
})")
("functionName", functionName)
("extractByteArrayLength", extractByteArrayLengthFunction())
("dataAreaFunction", arrayDataAreaFunction(_type))
("indexAccess", storageArrayIndexAccessFunction(_type))
("setToZero", storageSetToZeroFunction(*_type.baseType()))
("shl", shiftLeftFunctionDynamic())
.render();
});
}
string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
string functionName = "array_push_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array, value) {
let oldLen := <fetchLength>(array)
if iszero(lt(oldLen, <maxArrayLength>)) { invalid() }
sstore(array, add(oldLen, 1))
<?isByteArray>
let data := sload(array)
let oldLen := <extractByteArrayLength>(data)
if iszero(lt(oldLen, <maxArrayLength>)) { invalid() }
let slot, offset := <indexAccess>(array, oldLen)
<storeValue>(slot, offset, value)
switch gt(oldLen, 31)
case 0 {
value := byte(0, value)
switch oldLen
case 31 {
// Here we have special case when array switches from short array to long array
// We need to copy data
let dataArea := <dataAreaFunction>(array)
data := and(data, not(0xff))
sstore(dataArea, or(and(0xff, value), data))
// New length is 32, encoded as (32 * 2 + 1)
sstore(array, 65)
}
default {
data := add(data, 2)
let shiftBits := mul(8, sub(31, oldLen))
let valueShifted := <shl>(shiftBits, and(0xff, value))
let mask := <shl>(shiftBits, 0xff)
data := or(and(data, not(mask)), valueShifted)
sstore(array, data)
}
}
default {
sstore(array, add(data, 2))
let slot, offset := <indexAccess>(array, oldLen)
<storeValue>(slot, offset, value)
}
<!isByteArray>
let oldLen := sload(array)
if iszero(lt(oldLen, <maxArrayLength>)) { invalid() }
sstore(array, add(oldLen, 1))
let slot, offset := <indexAccess>(array, oldLen)
<storeValue>(slot, offset, value)
</isByteArray>
})")
("functionName", functionName)
("fetchLength", arrayLengthFunction(_type))
("extractByteArrayLength", _type.isByteArray() ? extractByteArrayLengthFunction() : "")
("dataAreaFunction", arrayDataAreaFunction(_type))
("isByteArray", _type.isByteArray())
("indexAccess", storageArrayIndexAccessFunction(_type))
("storeValue", updateStorageValueFunction(*_type.baseType()))
("maxArrayLength", (u256(1) << 64).str())
("shl", shiftLeftFunctionDynamic())
("shr", shiftRightFunction(248))
.render();
});
}
@ -947,21 +1051,33 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
{
solUnimplementedAssert(_type.baseType()->storageBytes() > 16, "");
string functionName = "storage_array_index_access_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array, index) -> slot, offset {
if iszero(lt(index, <arrayLen>(array))) {
invalid()
}
let arrayLength := <arrayLen>(array)
if iszero(lt(index, arrayLength)) { invalid() }
let data := <dataAreaFunc>(array)
<?multipleItemsPerSlot>
<?isBytesArray>
offset := sub(31, mod(index, 0x20))
switch lt(arrayLength, 0x20)
case 0 {
let dataArea := <dataAreaFunc>(array)
slot := add(dataArea, div(index, 0x20))
}
default {
slot := array
}
<!isBytesArray>
let itemsPerSlot := div(0x20, <storageBytes>)
let dataArea := <dataAreaFunc>(array)
slot := add(dataArea, div(index, itemsPerSlot))
offset := mod(index, itemsPerSlot)
</isBytesArray>
<!multipleItemsPerSlot>
slot := add(data, mul(index, <storageSize>))
let dataArea := <dataAreaFunc>(array)
slot := add(dataArea, mul(index, <storageSize>))
offset := 0
</multipleItemsPerSlot>
}
@ -970,7 +1086,9 @@ string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
("arrayLen", arrayLengthFunction(_type))
("dataAreaFunc", arrayDataAreaFunction(_type))
("multipleItemsPerSlot", _type.baseType()->storageBytes() <= 16)
("isBytesArray", _type.isByteArray())
("storageSize", _type.baseType()->storageSize().str())
("storageBytes", toString(_type.baseType()->storageBytes()))
.render();
});
}
@ -1158,7 +1276,8 @@ string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingT
string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes)
{
solUnimplementedAssert(!_splitFunctionTypes, "");
if (_type.category() == Type::Category::Function)
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName =
"read_from_storage_" +
string(_splitFunctionTypes ? "split_" : "") +
@ -1181,7 +1300,8 @@ string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool
string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes)
{
solUnimplementedAssert(!_splitFunctionTypes, "");
if (_type.category() == Type::Category::Function)
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName =
"read_from_storage_dynamic" +
string(_splitFunctionTypes ? "split_" : "") +
@ -1311,7 +1431,8 @@ string YulUtilFunctions::writeToMemoryFunction(Type const& _type)
string YulUtilFunctions::extractFromStorageValueDynamic(Type const& _type, bool _splitFunctionTypes)
{
solUnimplementedAssert(!_splitFunctionTypes, "");
if (_type.category() == Type::Category::Function)
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName =
"extract_from_storage_value_dynamic" +
@ -1356,7 +1477,8 @@ string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offs
string YulUtilFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes)
{
solAssert(_type.isValueType(), "");
solUnimplementedAssert(!_splitFunctionTypes, "");
if (_type.category() == Type::Category::Function)
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier();
return m_functionCollector.createFunction(functionName, [&] {
@ -1499,8 +1621,6 @@ string YulUtilFunctions::zeroComplexMemoryArrayFunction(ArrayType const& _type)
string YulUtilFunctions::allocateAndInitializeMemoryArrayFunction(ArrayType const& _type)
{
solUnimplementedAssert(!_type.isByteArray(), "");
string functionName = "allocate_and_zero_memory_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
@ -1551,8 +1671,10 @@ string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType co
for (size_t i = 0; i < members.size(); ++i)
{
solAssert(members[i]->memoryHeadSize() == 32, "");
solAssert(members[i]->dataStoredIn(DataLocation::Memory), "");
memberParams[i]["zeroValue"] = zeroValueFunction(*members[i], false);
memberParams[i]["zeroValue"] = zeroValueFunction(
*TypeProvider::withLocationIfReference(DataLocation::Memory, members[i]),
false
);
}
templ("member", memberParams);
return templ.render();
@ -2122,6 +2244,27 @@ string YulUtilFunctions::zeroValueFunction(Type const& _type, bool _splitFunctio
("functionName", functionName)
.render();
if (_type.dataStoredIn(DataLocation::CallData))
{
solAssert(
_type.category() == Type::Category::Struct ||
_type.category() == Type::Category::Array,
"");
Whiskers templ(R"(
function <functionName>() -> offset<?hasLength>, length</hasLength> {
offset := calldatasize()
<?hasLength> length := 0 </hasLength>
}
)");
templ("functionName", functionName);
templ("hasLength",
_type.category() == Type::Category::Array &&
dynamic_cast<ArrayType const&>(_type).isDynamicallySized()
);
return templ.render();
}
Whiskers templ(R"(
function <functionName>() -> ret {
ret := <zeroValue>
@ -2506,4 +2649,3 @@ string YulUtilFunctions::copyConstructorArgumentsToMemoryFunction(
.render();
});
}

View File

@ -364,6 +364,14 @@ private:
/// use exactly one variable to hold the value.
std::string conversionFunctionSpecial(Type const& _from, Type const& _to);
/// @returns function name that extracts and returns byte array length
/// signature: (data) -> length
std::string extractByteArrayLengthFunction();
/// @returns the name of a function that reduces the size of a storage byte array by one element
/// signature: (byteArray)
std::string storageByteArrayPopFunction(ArrayType const& _type);
std::string readFromMemoryOrCalldata(Type const& _type, bool _fromCalldata);
langutil::EVMVersion m_evmVersion;

View File

@ -0,0 +1,111 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libsolidity/codegen/ir/Common.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libsolutil/CommonIO.h>
using namespace std;
using namespace solidity::util;
using namespace solidity::frontend;
YulArity YulArity::fromType(FunctionType const& _functionType)
{
return YulArity{
TupleType(_functionType.parameterTypes()).sizeOnStack(),
TupleType(_functionType.returnParameterTypes()).sizeOnStack()
};
}
string IRNames::function(FunctionDefinition const& _function)
{
// @TODO previously, we had to distinguish creation context and runtime context,
// but since we do not work with jump positions anymore, this should not be a problem, right?
return "fun_" + _function.name() + "_" + to_string(_function.id());
}
string IRNames::function(VariableDeclaration const& _varDecl)
{
return "getter_fun_" + _varDecl.name() + "_" + to_string(_varDecl.id());
}
string IRNames::creationObject(ContractDefinition const& _contract)
{
return _contract.name() + "_" + toString(_contract.id());
}
string IRNames::runtimeObject(ContractDefinition const& _contract)
{
return _contract.name() + "_" + toString(_contract.id()) + "_deployed";
}
string IRNames::internalDispatch(YulArity const& _arity)
{
return "dispatch_internal"
"_in_" + to_string(_arity.in) +
"_out_" + to_string(_arity.out);
}
string IRNames::implicitConstructor(ContractDefinition const& _contract)
{
return "constructor_" + _contract.name() + "_" + to_string(_contract.id());
}
string IRNames::constantValueFunction(VariableDeclaration const& _constant)
{
solAssert(_constant.isConstant(), "");
return "constant_" + _constant.name() + "_" + to_string(_constant.id());
}
string IRNames::localVariable(VariableDeclaration const& _declaration)
{
return "vloc_" + _declaration.name() + '_' + std::to_string(_declaration.id());
}
string IRNames::localVariable(Expression const& _expression)
{
return "expr_" + to_string(_expression.id());
}
string IRNames::trySuccessConditionVariable(Expression const& _expression)
{
auto annotation = dynamic_cast<FunctionCallAnnotation const*>(&_expression.annotation());
solAssert(annotation, "");
solAssert(annotation->tryCall, "Parameter must be a FunctionCall with tryCall-annotation set.");
return "trySuccessCondition_" + to_string(_expression.id());
}
string IRNames::tupleComponent(size_t _i)
{
return "component_" + to_string(_i + 1);
}
string IRNames::zeroValue(Type const& _type, string const& _variableName)
{
return "zero_value_for_type_" + _type.identifier() + _variableName;
}
FunctionDefinition const* IRHelpers::referencedFunctionDeclaration(Expression const& _expression)
{
if (auto memberAccess = dynamic_cast<MemberAccess const*>(&_expression))
return dynamic_cast<FunctionDefinition const*>(memberAccess->annotation().referencedDeclaration);
else if (auto identifier = dynamic_cast<Identifier const*>(&_expression))
return dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration);
else
return nullptr;
}

View File

@ -0,0 +1,82 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Miscellaneous utilities for use in IR generator.
*/
#pragma once
#include <libsolidity/ast/AST.h>
#include <algorithm>
#include <string>
namespace solidity::frontend
{
/**
* Structure that describes arity and co-arity of a Yul function, i.e. the number of its inputs and outputs.
*/
struct YulArity
{
explicit YulArity(size_t _in, size_t _out): in(_in), out(_out) {}
static YulArity fromType(FunctionType const& _functionType);
bool operator==(YulArity const& _other) const { return in == _other.in && out == _other.out; }
bool operator!=(YulArity const& _other) const { return !(*this == _other); }
size_t in; /// Number of input parameters
size_t out; /// Number of output parameters
};
struct IRNames
{
static std::string function(FunctionDefinition const& _function);
static std::string function(VariableDeclaration const& _varDecl);
static std::string creationObject(ContractDefinition const& _contract);
static std::string runtimeObject(ContractDefinition const& _contract);
static std::string internalDispatch(YulArity const& _arity);
static std::string implicitConstructor(ContractDefinition const& _contract);
static std::string constantValueFunction(VariableDeclaration const& _constant);
static std::string localVariable(VariableDeclaration const& _declaration);
static std::string localVariable(Expression const& _expression);
/// @returns the variable name that can be used to inspect the success or failure of an external
/// function call that was invoked as part of the try statement.
static std::string trySuccessConditionVariable(Expression const& _expression);
static std::string tupleComponent(size_t _i);
static std::string zeroValue(Type const& _type, std::string const& _variableName);
};
struct IRHelpers
{
static FunctionDefinition const* referencedFunctionDeclaration(Expression const& _expression);
};
}
// Overloading std::less() makes it possible to use YulArity as a map key. We could define operator<
// instead but such an operator would be a bit ambiguous (e.g. YulArity{2, 2} would be be greater than
// YulArity{1, 10} in lexicographical order but the latter has greater total number of inputs and outputs).
template<>
struct std::less<solidity::frontend::YulArity>
{
bool operator() (solidity::frontend::YulArity const& _lhs, solidity::frontend::YulArity const& _rhs) const
{
return _lhs.in < _rhs.in || (_lhs.in == _rhs.in && _lhs.out < _rhs.out);
}
};

View File

@ -29,6 +29,8 @@
#include <libsolutil/Whiskers.h>
#include <libsolutil/StringUtils.h>
#include <boost/range/adaptor/map.hpp>
using namespace std;
using namespace solidity;
using namespace solidity::util;
@ -36,7 +38,7 @@ using namespace solidity::frontend;
string IRGenerationContext::enqueueFunctionForCodeGeneration(FunctionDefinition const& _function)
{
string name = functionName(_function);
string name = IRNames::function(_function);
if (!m_functions.contains(name))
m_functionGenerationQueue.insert(&_function);
@ -116,94 +118,60 @@ void IRGenerationContext::addStateVariable(
m_stateVariables[&_declaration] = make_pair(move(_storageOffset), _byteOffset);
}
string IRGenerationContext::functionName(FunctionDefinition const& _function)
{
// @TODO previously, we had to distinguish creation context and runtime context,
// but since we do not work with jump positions anymore, this should not be a problem, right?
return "fun_" + _function.name() + "_" + to_string(_function.id());
}
string IRGenerationContext::functionName(VariableDeclaration const& _varDecl)
{
return "getter_fun_" + _varDecl.name() + "_" + to_string(_varDecl.id());
}
string IRGenerationContext::creationObjectName(ContractDefinition const& _contract) const
{
return _contract.name() + "_" + toString(_contract.id());
}
string IRGenerationContext::runtimeObjectName(ContractDefinition const& _contract) const
{
return _contract.name() + "_" + toString(_contract.id()) + "_deployed";
}
string IRGenerationContext::newYulVariable()
{
return "_" + to_string(++m_varCounter);
}
string IRGenerationContext::trySuccessConditionVariable(Expression const& _expression) const
void IRGenerationContext::initializeInternalDispatch(InternalDispatchMap _internalDispatch)
{
// NB: The TypeChecker already ensured that the Expression is of type FunctionCall.
solAssert(
static_cast<FunctionCallAnnotation const&>(_expression.annotation()).tryCall,
"Parameter must be a FunctionCall with tryCall-annotation set."
);
solAssert(internalDispatchClean(), "");
return "trySuccessCondition_" + to_string(_expression.id());
for (set<FunctionDefinition const*> const& functions: _internalDispatch | boost::adaptors::map_values)
for (auto function: functions)
enqueueFunctionForCodeGeneration(*function);
m_internalDispatchMap = move(_internalDispatch);
}
string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
InternalDispatchMap IRGenerationContext::consumeInternalDispatchMap()
{
string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
return m_functions.createFunction(funName, [&]() {
Whiskers templ(R"(
function <functionName>(fun <comma> <in>) <arrow> <out> {
switch fun
<#cases>
case <funID>
{
<out> <assignment_op> <name>(<in>)
}
</cases>
default { invalid() }
}
)");
templ("functionName", funName);
templ("comma", _in > 0 ? "," : "");
YulUtilFunctions utils(m_evmVersion, m_revertStrings, m_functions);
templ("in", suffixedVariableNameList("in_", 0, _in));
templ("arrow", _out > 0 ? "->" : "");
templ("assignment_op", _out > 0 ? ":=" : "");
templ("out", suffixedVariableNameList("out_", 0, _out));
m_directInternalFunctionCalls.clear();
// UNIMPLEMENTED: Internal library calls via pointers are not implemented yet.
// We're not generating code for internal library functions here even though it's possible
// to call them via pointers. Right now such calls end up triggering the `default` case in
// the switch above.
vector<map<string, string>> functions;
for (auto const& contract: mostDerivedContract().annotation().linearizedBaseContracts)
for (FunctionDefinition const* function: contract->definedFunctions())
if (
FunctionType const* functionType = TypeProvider::function(*function)->asCallableFunction(false);
!function->isConstructor() &&
TupleType(functionType->parameterTypes()).sizeOnStack() == _in &&
TupleType(functionType->returnParameterTypes()).sizeOnStack() == _out
)
{
// 0 is reserved for uninitialized function pointers
solAssert(function->id() != 0, "Unexpected function ID: 0");
InternalDispatchMap internalDispatch = move(m_internalDispatchMap);
m_internalDispatchMap.clear();
return internalDispatch;
}
functions.emplace_back(map<string, string> {
{ "funID", to_string(function->id()) },
{ "name", functionName(*function)}
});
void IRGenerationContext::internalFunctionCalledDirectly(Expression const& _expression)
{
solAssert(m_directInternalFunctionCalls.count(&_expression) == 0, "");
enqueueFunctionForCodeGeneration(*function);
}
templ("cases", move(functions));
return templ.render();
});
m_directInternalFunctionCalls.insert(&_expression);
}
void IRGenerationContext::internalFunctionAccessed(Expression const& _expression, FunctionDefinition const& _function)
{
solAssert(
IRHelpers::referencedFunctionDeclaration(_expression) &&
_function.resolveVirtual(mostDerivedContract()) ==
IRHelpers::referencedFunctionDeclaration(_expression)->resolveVirtual(mostDerivedContract()),
"Function definition does not match the expression"
);
if (m_directInternalFunctionCalls.count(&_expression) == 0)
{
FunctionType const* functionType = TypeProvider::function(_function, FunctionType::Kind::Internal);
solAssert(functionType, "");
m_internalDispatchMap[YulArity::fromType(*functionType)].insert(&_function);
enqueueFunctionForCodeGeneration(_function);
}
}
void IRGenerationContext::internalFunctionCalledThroughDispatch(YulArity const& _arity)
{
m_internalDispatchMap.try_emplace(_arity);
}
YulUtilFunctions IRGenerationContext::utils()
@ -220,4 +188,3 @@ std::string IRGenerationContext::revertReasonIfDebug(std::string const& _message
{
return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message);
}

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