mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #9126 from ethereum/develop
Merge develop into release for 0.6.9
This commit is contained in:
commit
3e3065ac00
@ -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
|
||||
|
65
.circleci/docker/Dockerfile.emscripten
Normal file
65
.circleci/docker/Dockerfile.emscripten
Normal 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
|
@ -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; \
|
||||
|
@ -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/*
|
||||
|
||||
|
@ -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 ;
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
37
Changelog.md
37
Changelog.md
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -1163,5 +1163,9 @@
|
||||
"0.6.8": {
|
||||
"bugs": [],
|
||||
"released": "2020-05-14"
|
||||
},
|
||||
"0.6.9": {
|
||||
"bugs": [],
|
||||
"released": "2020-06-04"
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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),
|
||||
|
@ -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)``
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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; }
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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.");
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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]; }
|
||||
|
@ -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());
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ protected:
|
||||
};
|
||||
|
||||
/// Location of the current token
|
||||
SourceLocation currentLocation() const;
|
||||
virtual SourceLocation currentLocation() const;
|
||||
|
||||
///@{
|
||||
///@name Helper functions
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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{
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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';
|
||||
}
|
||||
|
@ -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&)
|
||||
|
@ -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);
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
@ -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
35
libsmtutil/CMakeLists.txt
Normal 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()
|
@ -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();
|
||||
}
|
@ -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;
|
@ -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)
|
||||
|
||||
}
|
@ -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";
|
||||
}
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
@ -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
37
libsmtutil/Sorts.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
@ -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();
|
||||
}
|
@ -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;
|
@ -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()
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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."
|
||||
);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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.");
|
||||
|
@ -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;
|
||||
|
@ -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 \"" +
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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,
|
||||
|
@ -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(), "");
|
||||
|
@ -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; }
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
|
@ -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.
|
||||
|
@ -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));
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
111
libsolidity/codegen/ir/Common.cpp
Normal file
111
libsolidity/codegen/ir/Common.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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;
|
||||
}
|
82
libsolidity/codegen/ir/Common.h
Normal file
82
libsolidity/codegen/ir/Common.h
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
};
|
@ -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
Loading…
Reference in New Issue
Block a user