Merge pull request #8468 from ethereum/develop

Merge develop into release for 0.6.4
This commit is contained in:
chriseth 2020-03-10 15:24:17 +01:00 committed by GitHub
commit 1dca32f352
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
266 changed files with 5031 additions and 1119 deletions

View File

@ -11,7 +11,7 @@ docker build -t ethereum/solidity-buildpack-deps:ubuntu1904-<revision> -f Docker
docker push ethereum/solidity-buildpack-deps:ubuntu1904-<revision>
```
The current revision is stored in a [circle ci pipeline parameter](https://github.com/CircleCI-Public/api-preview-docs/blob/master/docs/pipeline-parameters.md#pipeline-parameters) called `docker-image-rev`. Please update the value assigned to this parameter at the time of a docker image update. Please verify that the value assigned to the parameter matches the revision part of the docker image tag (`<revision>` in the docker build/push snippet shown above). Otherwise, the docker image used by circle ci and the one actually pushed to docker hub will differ.
The current revisions per docker image are stored in [circle ci pipeline parameters](https://github.com/CircleCI-Public/api-preview-docs/blob/master/docs/pipeline-parameters.md#pipeline-parameters) called `<image-desc>-docker-image-rev` (e.g., `ubuntu-1904-docker-image-rev`). Please update the value assigned to the parameter(s) corresponding to the docker image(s) being updated at the time of the update. Please verify that the value assigned to the parameter matches the revision part of the docker image tag (`<revision>` in the docker build/push snippet shown above). Otherwise, the docker image used by circle ci and the one actually pushed to docker hub will differ.
Once the docker image has been built and pushed to Dockerhub, you can find it at:

View File

@ -7,9 +7,18 @@
# - ems: Emscripten
version: 2.1
parameters:
docker-image-rev:
ubuntu-1804-docker-image-rev:
type: string
default: "4"
ubuntu-1904-docker-image-rev:
type: string
default: "4"
ubuntu-1904-clang-docker-image-rev:
type: string
default: "5"
ubuntu-1604-clang-ossfuzz-docker-image-rev:
type: string
default: "2"
defaults:
@ -113,9 +122,20 @@ defaults:
name: command line tests
command: ./test/cmdlineTests.sh
- test_ubuntu1604_clang: &test_ubuntu1604_clang
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1604-clang-ossfuzz-<< pipeline.parameters.ubuntu-1604-clang-ossfuzz-docker-image-rev >>
steps:
- checkout
- attach_workspace:
at: build
- run: *run_soltest
- store_test_results: *store_test_results
- store_artifacts: *artifacts_test_results
- test_ubuntu1904_clang: &test_ubuntu1904_clang
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1904-clang-<< pipeline.parameters.docker-image-rev >>
- image: ethereum/solidity-buildpack-deps:ubuntu1904-clang-<< pipeline.parameters.ubuntu-1904-clang-docker-image-rev >>
steps:
- checkout
- attach_workspace:
@ -126,7 +146,7 @@ defaults:
- test_ubuntu1904: &test_ubuntu1904
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.docker-image-rev >>
- image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.ubuntu-1904-docker-image-rev >>
steps:
- checkout
- attach_workspace:
@ -160,6 +180,11 @@ defaults:
requires:
- b_ubu
- workflow_ubuntu1604_clang: &workflow_ubuntu1604_clang
<<: *workflow_trigger_on_tags
requires:
- b_ubu_ossfuzz
- workflow_ubuntu1904_clang: &workflow_ubuntu1904_clang
<<: *workflow_trigger_on_tags
requires:
@ -190,7 +215,7 @@ defaults:
requires:
- b_ems
- workflow_ubuntu1904_ossfuzz: &workflow_ubuntu1904_ossfuzz
- workflow_ubuntu1604_ossfuzz: &workflow_ubuntu1604_ossfuzz
<<: *workflow_trigger_on_tags
requires:
- b_ubu_ossfuzz
@ -312,7 +337,7 @@ jobs:
b_ubu_clang: &build_ubuntu1904_clang
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1904-clang-<< pipeline.parameters.docker-image-rev >>
- image: ethereum/solidity-buildpack-deps:ubuntu1904-clang-<< pipeline.parameters.ubuntu-1904-clang-docker-image-rev >>
environment:
CC: clang
CXX: clang++
@ -324,7 +349,7 @@ jobs:
b_ubu: &build_ubuntu1904
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.docker-image-rev >>
- image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.ubuntu-1904-docker-image-rev >>
steps:
- checkout
- run: *run_build
@ -339,7 +364,7 @@ jobs:
b_ubu18: &build_ubuntu1804
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1804-<< pipeline.parameters.docker-image-rev >>
- image: ethereum/solidity-buildpack-deps:ubuntu1804-<< pipeline.parameters.ubuntu-1804-docker-image-rev >>
environment:
CMAKE_OPTIONS: -DCMAKE_CXX_FLAGS=-O2
CMAKE_BUILD_TYPE: RelWithDebugInfo
@ -391,12 +416,13 @@ jobs:
- checkout
- run: *run_build
b_ubu_ossfuzz:
<<: *build_ubuntu1904_clang
b_ubu_ossfuzz: &build_ubuntu1604_clang
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1604-clang-ossfuzz-<< pipeline.parameters.ubuntu-1604-clang-ossfuzz-docker-image-rev >>
environment:
TERM: xterm
CC: clang
CXX: clang++
TERM: xterm
CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake
steps:
- checkout
@ -405,7 +431,7 @@ jobs:
- persist_to_workspace: *artifacts_executables_ossfuzz
t_ubu_ossfuzz: &t_ubu_ossfuzz
<<: *test_ubuntu1904_clang
<<: *test_ubuntu1604_clang
steps:
- checkout
- attach_workspace:
@ -546,7 +572,7 @@ jobs:
b_docs:
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.docker-image-rev >>
- image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.ubuntu-1904-docker-image-rev >>
steps:
- checkout
- run: *setup_prerelease_commit_hash
@ -571,7 +597,7 @@ jobs:
t_ubu_cli: &t_ubu_cli
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.docker-image-rev >>
- image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.ubuntu-1904-docker-image-rev >>
environment:
TERM: xterm
steps:
@ -793,7 +819,7 @@ workflows:
jobs:
# OSSFUZZ builds and (regression) tests
- b_ubu_ossfuzz: *workflow_trigger_on_tags
- t_ubu_ossfuzz: *workflow_ubuntu1904_ossfuzz
- t_ubu_ossfuzz: *workflow_ubuntu1604_ossfuzz
# Code Coverage enabled build and tests
- b_ubu_codecov: *workflow_trigger_on_tags

View File

@ -0,0 +1,101 @@
# vim:syntax=dockerfile
#------------------------------------------------------------------------------
# Dockerfile for building and testing Solidity Compiler on CI
# Target: Ubuntu 16.04 (Xenial Xerus) ossfuzz Clang variant
# 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.
#------------------------------------------------------------------------------
FROM gcr.io/oss-fuzz-base/base-clang as base
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update; \
apt-get -qqy install --no-install-recommends \
build-essential \
software-properties-common \
ninja-build git wget \
libbz2-dev zlib1g-dev git curl; \
apt-get install -qy python-pip python-sphinx;
# Install cmake 3.14 (minimum requirement is cmake 3.10)
RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.5/cmake-3.14.5-Linux-x86_64.sh; \
chmod +x cmake-3.14.5-Linux-x86_64.sh; \
./cmake-3.14.5-Linux-x86_64.sh --skip-license --prefix="/usr"
FROM base AS libraries
# Boost
RUN git clone -b boost-1.69.0 https://github.com/boostorg/boost.git \
/usr/src/boost; \
cd /usr/src/boost; \
git submodule update --init --recursive; \
./bootstrap.sh --with-toolset=clang --prefix=/usr; \
./b2 toolset=clang cxxflags="-stdlib=libc++" linkflags="-stdlib=libc++" headers; \
./b2 toolset=clang cxxflags="-stdlib=libc++" linkflags="-stdlib=libc++" \
link=static variant=release runtime-link=static \
system filesystem unit_test_framework program_options \
install -j $(($(nproc)/2)); \
rm -rf /usr/src/boost
# Z3
RUN git clone --depth 1 -b z3-4.8.7 https://github.com/Z3Prover/z3.git \
/usr/src/z3; \
cd /usr/src/z3; \
mkdir build; \
cd build; \
LDFLAGS=$CXXFLAGS cmake -DZ3_BUILD_LIBZ3_SHARED=OFF -DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_BUILD_TYPE=Release ..; \
make libz3 -j; \
make install; \
rm -rf /usr/src/z3
# OSSFUZZ: libprotobuf-mutator
RUN set -ex; \
git clone https://github.com/google/libprotobuf-mutator.git \
/usr/src/libprotobuf-mutator; \
cd /usr/src/libprotobuf-mutator; \
git checkout 3521f47a2828da9ace403e4ecc4aece1a84feb36; \
mkdir build; \
cd build; \
cmake .. -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON \
-DLIB_PROTO_MUTATOR_TESTING=OFF -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="/usr"; \
ninja; \
cp -vpr external.protobuf/bin/* /usr/bin/; \
cp -vpr external.protobuf/include/* /usr/include/; \
cp -vpr external.protobuf/lib/* /usr/lib/; \
ninja install/strip; \
rm -rf /usr/src/libprotobuf-mutator
# EVMONE
RUN set -ex; \
cd /usr/src; \
git clone --branch="v0.4.0" --recurse-submodules https://github.com/ethereum/evmone.git; \
cd evmone; \
mkdir build; \
cd build; \
cmake -G Ninja -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX="/usr" ..; \
ninja; \
ninja install/strip; \
rm -rf /usr/src/evmone
FROM base
COPY --from=libraries /usr/lib /usr/lib
COPY --from=libraries /usr/bin /usr/bin
COPY --from=libraries /usr/include /usr/include

View File

@ -51,9 +51,10 @@ ENV CC clang
ENV CXX clang++
# Boost
RUN git clone --recursive -b boost-1.69.0 https://github.com/boostorg/boost.git \
RUN git clone -b boost-1.69.0 https://github.com/boostorg/boost.git \
/usr/src/boost; \
cd /usr/src/boost; \
git submodule update --init --recursive; \
./bootstrap.sh --with-toolset=clang --prefix=/usr; \
./b2 toolset=clang headers; \
./b2 toolset=clang variant=release \
@ -76,7 +77,7 @@ RUN set -ex; \
git clone https://github.com/google/libprotobuf-mutator.git \
/usr/src/libprotobuf-mutator; \
cd /usr/src/libprotobuf-mutator; \
git checkout d1fe8a7d8ae18f3d454f055eba5213c291986f21; \
git checkout 3521f47a2828da9ace403e4ecc4aece1a84feb36; \
mkdir build; \
cd build; \
cmake .. -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON \

View File

@ -10,7 +10,7 @@ include(EthPolicy)
eth_policy()
# project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.6.3")
set(PROJECT_VERSION "0.6.4")
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
include(TestBigEndian)

View File

@ -1,3 +1,24 @@
### 0.6.4 (2020-03-10)
Language Features:
* General: Deprecated `value(...)` and `gas(...)` in favor of `{value: ...}` and `{gas: ...}`
* Inline Assembly: Allow assigning to `_slot` of local storage variable pointers.
* Inline Assembly: Perform control flow analysis on inline assembly. Allows storage returns to be set in assembly only.
Compiler Features:
* AssemblyStack: Support for source locations (source mappings) and thus debugging Yul sources.
* Commandline Interface: Enable output of experimental optimized IR via ``--ir-optimized``.
Bugfixes:
* Inheritance: Fix incorrect error on calling unimplemented base functions.
* Reference Resolver: Fix scoping issue following try/catch statements.
* Standard-JSON-Interface: Fix a bug related to empty filenames and imports.
* SMTChecker: Fix internal errors when analysing tuples.
* Yul AST Import: correctly import blocks as statements, switch statements and string literals.
### 0.6.3 (2020-02-18)
Language Features:
@ -6,7 +27,6 @@ Language Features:
* Report source locations for structured documentation errors.
Compiler Features:
* AST: Add a new node for doxygen-style, structured documentation that can be received by contract, function, event and modifier definitions.
* Code Generator: Use ``calldatacopy`` instead of ``codecopy`` to zero out memory past input.
@ -20,7 +40,6 @@ Bugfixes:
* Type Checker: Make invalid calls to uncallable types fatal errors instead of regular.
### 0.6.2 (2020-01-27)
Language Features:

View File

@ -64,7 +64,7 @@ build_script:
- msbuild solidity.sln /p:Configuration=%CONFIGURATION% /m:%NUMBER_OF_PROCESSORS% /v:minimal
- cd %APPVEYOR_BUILD_FOLDER%
- scripts\release.bat %CONFIGURATION% 2017
- ps: $bytecodedir = git show -s --format="%cd-%H" --date=short
- ps: $bytecodedir = git show -s --format="%cd-%H" --date="format:%Y-%m-%d-%H-%M"
test_script:
- cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION%

View File

@ -8,4 +8,8 @@ set(OSSFUZZ ON CACHE BOOL "Enable fuzzer build" FORCE)
# Use libfuzzer as the fuzzing back-end
set(LIB_FUZZING_ENGINE "-fsanitize=fuzzer" CACHE STRING "Use libfuzzer back-end" FORCE)
# clang/libfuzzer specific flags for UBSan instrumentation
set(CMAKE_CXX_FLAGS "-O1 -gline-tables-only -fsanitize=undefined -fsanitize=fuzzer-no-link -stdlib=libstdc++" CACHE STRING "Custom compilation flags" FORCE)
set(CMAKE_CXX_FLAGS "-O1 -fno-omit-frame-pointer -gline-tables-only -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -I /usr/local/include/c++/v1 -fsanitize=undefined -fsanitize=fuzzer-no-link -stdlib=libc++" CACHE STRING "Custom compilation flags" FORCE)
# Link statically against boost libraries
set(BOOST_FOUND ON CACHE BOOL "" FORCE)
set(Boost_USE_STATIC_LIBS ON CACHE BOOL "Link against static Boost libraries" FORCE)
set(Boost_USE_STATIC_RUNTIME ON CACHE BOOL "Link against static Boost runtime library" FORCE)

View File

@ -11,7 +11,7 @@ You can interleave Solidity statements with inline assembly in a language close
to the one of the Ethereum virtual machine. This gives you more fine-grained control,
which is especially useful when you are enhancing the language by writing libraries.
The language used for inline assembly in Solidity is called `Yul <yul>`_
The language used for inline assembly in Solidity is called :ref:`Yul <yul>`
and it is documented in its own section. This section will only cover
how the inline assembly code can interface with the surrounding Solidity code.
@ -24,7 +24,7 @@ how the inline assembly code can interface with the surrounding Solidity code.
An inline assembly block is marked by ``assembly { ... }``, where the code inside
the curly braces is code in the `Yul <yul>`_ language.
the curly braces is code in the :ref:`Yul <yul>` language.
The inline assembly code can access local Solidity variables as explained below.
@ -172,6 +172,11 @@ Assignments are possible to assembly-local variables and to function-local
variables. Take care that when you assign to variables that point to
memory or storage, you will only change the pointer and not the data.
You can assign to the ``_slot`` part of a local storage variable pointer.
For these (structs, arrays or mappings), the ``_offset`` part is always zero.
It is not possible to assign to the ``_slot`` or ``_offset`` part of a state variable,
though.
Things to Avoid
@ -225,4 +230,3 @@ first slot of the array and followed by the array elements.
Statically-sized memory arrays do not have a length field, but it might be added later
to allow better convertibility between statically- and dynamically-sized arrays, so
do not rely on this.

View File

@ -888,5 +888,9 @@
"0.6.3": {
"bugs": [],
"released": "2020-02-18"
},
"0.6.4": {
"bugs": [],
"released": "2020-03-10"
}
}

View File

@ -252,7 +252,7 @@ which only need to be created if there is a dispute.
/// 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(bytes20(keccak256(abi.encodePacked(
address predictedAddress = address(uint(keccak256(abi.encodePacked(
byte(0xff),
address(this),
salt,
@ -343,18 +343,8 @@ i.e. the following is not valid: ``(x, uint y) = (1, 2);``
Complications for Arrays and Structs
------------------------------------
The semantics of assignments are a bit more complicated for
non-value types like arrays and structs.
Assigning *to* a state variable always creates an independent
copy. On the other hand, assigning to a local variable creates
an independent copy only for elementary types, i.e. static
types that fit into 32 bytes. If structs or arrays (including
``bytes`` and ``string``) are assigned from a state variable
to a local variable, the local variable holds a reference to
the original state variable. A second assignment to the local
variable does not modify the state but only changes the
reference. Assignments to members (or elements) of the local
variable *do* change the state.
The semantics of assignments are more complicated for non-value types like arrays and structs,
including ``bytes`` and ``string``, see :ref:`Data location and assignment behaviour <data-location-assignment>` for details.
In the example below the call to ``g(x)`` has no effect on ``x`` because it creates
an independent copy of the storage value in memory. However, ``h(x)`` successfully modifies ``x``

View File

@ -646,7 +646,7 @@ External (or public) functions have the following members:
Example that shows how to use the members::
pragma solidity >=0.4.16 <0.7.0;
// This will report a warning
contract Example {
function f() public payable returns (bytes4) {

View File

@ -19,12 +19,14 @@
#include <libsolutil/CommonData.h>
#include <libsolutil/FixedHash.h>
#include <liblangutil/SourceLocation.h>
#include <fstream>
using namespace std;
using namespace solidity;
using namespace solidity::evmasm;
using namespace solidity::langutil;
static_assert(sizeof(size_t) <= 8, "size_t must be at most 64-bits wide");
@ -281,3 +283,92 @@ ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item)
}
return _out;
}
std::string AssemblyItem::computeSourceMapping(
AssemblyItems const& _items,
map<string, unsigned> const& _sourceIndicesMap
)
{
string ret;
int prevStart = -1;
int prevLength = -1;
int prevSourceIndex = -1;
size_t prevModifierDepth = -1;
char prevJump = 0;
for (auto const& item: _items)
{
if (!ret.empty())
ret += ";";
SourceLocation const& location = item.location();
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()) :
-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;
unsigned components = 5;
if (modifierDepth == prevModifierDepth)
{
components--;
if (jump == prevJump)
{
components--;
if (sourceIndex == prevSourceIndex)
{
components--;
if (length == prevLength)
{
components--;
if (location.start == prevStart)
components--;
}
}
}
}
if (components-- > 0)
{
if (location.start != prevStart)
ret += to_string(location.start);
if (components-- > 0)
{
ret += ':';
if (length != prevLength)
ret += to_string(length);
if (components-- > 0)
{
ret += ':';
if (sourceIndex != prevSourceIndex)
ret += to_string(sourceIndex);
if (components-- > 0)
{
ret += ':';
if (jump != prevJump)
ret += jump;
if (components-- > 0)
{
ret += ':';
if (modifierDepth != prevModifierDepth)
ret += to_string(modifierDepth);
}
}
}
}
}
prevStart = location.start;
prevLength = length;
prevSourceIndex = sourceIndex;
prevJump = jump;
prevModifierDepth = modifierDepth;
}
return ret;
}

View File

@ -48,6 +48,8 @@ enum AssemblyItemType {
};
class Assembly;
class AssemblyItem;
using AssemblyItems = std::vector<AssemblyItem>;
class AssemblyItem
{
@ -122,6 +124,11 @@ public:
}
bool operator!=(Instruction _instr) const { return !operator==(_instr); }
static std::string computeSourceMapping(
AssemblyItems const& _items,
std::map<std::string, unsigned> const& _sourceIndicesMap
);
/// @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;
@ -157,8 +164,6 @@ private:
mutable std::shared_ptr<u256> m_pushedValue;
};
using AssemblyItems = std::vector<AssemblyItem>;
inline size_t bytesRequired(AssemblyItems const& _items, size_t _addressLength)
{
size_t size = 0;

View File

@ -155,6 +155,26 @@ bool SemanticInformation::terminatesControlFlow(Instruction _instruction)
}
}
bool SemanticInformation::reverts(AssemblyItem const& _item)
{
if (_item.type() != Operation)
return false;
else
return reverts(_item.instruction());
}
bool SemanticInformation::reverts(Instruction _instruction)
{
switch (_instruction)
{
case Instruction::INVALID:
case Instruction::REVERT:
return true;
default:
return false;
}
}
bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
{
if (_item.type() != Operation)

View File

@ -47,6 +47,8 @@ struct SemanticInformation
static bool altersControlFlow(AssemblyItem const& _item);
static bool terminatesControlFlow(AssemblyItem const& _item);
static bool terminatesControlFlow(Instruction _instruction);
static bool reverts(AssemblyItem const& _item);
static bool reverts(Instruction _instruction);
/// @returns false if the value put on the stack by _item depends on anything else than
/// the information in the current block header, memory, storage or stack.
static bool isDeterministic(AssemblyItem const& _item);

View File

@ -98,6 +98,7 @@ public:
std::string const& source() const noexcept { return m_source->source(); }
std::shared_ptr<CharStream> charStream() noexcept { return m_source; }
std::shared_ptr<CharStream const> charStream() const noexcept { return m_source; }
/// Resets the scanner as if newly constructed with _source as input.
void reset(CharStream _source);

View File

@ -166,6 +166,7 @@ namespace solidity::langutil
K(Indexed, "indexed", 0) \
K(Interface, "interface", 0) \
K(Internal, "internal", 0) \
K(Immutable, "immutable", 0) \
K(Import, "import", 0) \
K(Is, "is", 0) \
K(Library, "library", 0) \
@ -243,7 +244,6 @@ namespace solidity::langutil
K(Default, "default", 0) \
K(Define, "define", 0) \
K(Final, "final", 0) \
K(Immutable, "immutable", 0) \
K(Implements, "implements", 0) \
K(In, "in", 0) \
K(Inline, "inline", 0) \

View File

@ -37,7 +37,7 @@ bool ControlFlowAnalyzer::visit(FunctionDefinition const& _function)
{
auto const& functionFlow = m_cfg.functionFlow(_function);
checkUninitializedAccess(functionFlow.entry, functionFlow.exit);
checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert);
checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert, functionFlow.transactionReturn);
}
return false;
}
@ -137,7 +137,7 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
m_errorReporter.typeError(
variableOccurrence->occurrence() ?
variableOccurrence->occurrence()->location() :
*variableOccurrence->occurrence() :
variableOccurrence->declaration().location(),
ssl,
string("This variable is of storage pointer type and can be ") +
@ -148,7 +148,7 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
}
}
void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const
void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert, CFGNode const* _transactionReturn) const
{
// collect all nodes reachable from the entry point
std::set<CFGNode const*> reachable = util::BreadthFirstSearch<CFGNode const*>{{_entry}}.run(
@ -158,10 +158,10 @@ void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const*
}
).visited;
// traverse all paths backwards from exit and revert
// traverse all paths backwards from exit, revert and transaction return
// and extract (valid) source locations of unreachable nodes into sorted set
std::set<SourceLocation> unreachable;
util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert}}.run(
util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert, _transactionReturn}}.run(
[&](CFGNode const* _node, auto&& _addChild) {
if (!reachable.count(_node) && _node->location.isValid())
unreachable.insert(_node->location);

View File

@ -36,9 +36,9 @@ public:
private:
/// Checks for uninitialized variable accesses in the control flow between @param _entry and @param _exit.
void checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit) const;
/// Checks for unreachable code, i.e. code ending in @param _exit or @param _revert
/// Checks for unreachable code, i.e. code ending in @param _exit, @param _revert or @param _transactionReturn
/// that can not be reached from @param _entry.
void checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const;
void checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert, CFGNode const* _transactionReturn) const;
CFG const& m_cfg;
langutil::ErrorReporter& m_errorReporter;

View File

@ -16,6 +16,8 @@
*/
#include <libsolidity/analysis/ControlFlowBuilder.h>
#include <libyul/AsmData.h>
#include <libyul/backends/evm/EVMDialect.h>
using namespace solidity;
using namespace solidity::langutil;
@ -26,10 +28,12 @@ ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, Funct
m_nodeContainer(_nodeContainer),
m_currentNode(_functionFlow.entry),
m_returnNode(_functionFlow.exit),
m_revertNode(_functionFlow.revert)
m_revertNode(_functionFlow.revert),
m_transactionReturnNode(_functionFlow.transactionReturn)
{
}
unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
CFG::NodeContainer& _nodeContainer,
FunctionDefinition const& _function
@ -39,6 +43,7 @@ unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
functionFlow->entry = _nodeContainer.newNode();
functionFlow->exit = _nodeContainer.newNode();
functionFlow->revert = _nodeContainer.newNode();
functionFlow->transactionReturn = _nodeContainer.newNode();
ControlFlowBuilder builder(_nodeContainer, *functionFlow);
builder.appendControlFlow(_function);
@ -131,17 +136,17 @@ bool ControlFlowBuilder::visit(ForStatement const& _forStatement)
if (_forStatement.condition())
appendControlFlow(*_forStatement.condition());
auto loopExpression = newLabel();
auto postPart = newLabel();
auto nodes = splitFlow<2>();
auto afterFor = nodes[1];
m_currentNode = nodes[0];
{
BreakContinueScope scope(*this, afterFor, loopExpression);
BreakContinueScope scope(*this, afterFor, postPart);
appendControlFlow(_forStatement.body());
}
placeAndConnectLabel(loopExpression);
placeAndConnectLabel(postPart);
if (auto expression = _forStatement.loopExpression())
appendControlFlow(*expression);
@ -315,8 +320,7 @@ bool ControlFlowBuilder::visit(FunctionDefinition const& _functionDefinition)
appendControlFlow(*returnParameter);
m_returnNode->variableOccurrences.emplace_back(
*returnParameter,
VariableOccurrence::Kind::Return,
nullptr
VariableOccurrence::Kind::Return
);
}
@ -345,7 +349,7 @@ bool ControlFlowBuilder::visit(Return const& _return)
m_currentNode->variableOccurrences.emplace_back(
*returnParameter,
VariableOccurrence::Kind::Assignment,
&_return
_return.location()
);
}
connect(m_currentNode, m_returnNode);
@ -363,18 +367,158 @@ bool ControlFlowBuilder::visit(FunctionTypeName const& _functionTypeName)
bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly)
{
solAssert(!!m_currentNode, "");
visitNode(_inlineAssembly);
for (auto const& ref: _inlineAssembly.annotation().externalReferences)
solAssert(!!m_currentNode && !m_inlineAssembly, "");
m_inlineAssembly = &_inlineAssembly;
(*this)(_inlineAssembly.operations());
m_inlineAssembly = nullptr;
return false;
}
void ControlFlowBuilder::visit(yul::Statement const& _statement)
{
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(ref.second.declaration))
solAssert(m_currentNode && m_inlineAssembly, "");
m_currentNode->location = langutil::SourceLocation::smallestCovering(m_currentNode->location, locationOf(_statement));
ASTWalker::visit(_statement);
}
void ControlFlowBuilder::operator()(yul::If const& _if)
{
solAssert(m_currentNode && m_inlineAssembly, "");
visit(*_if.condition);
auto nodes = splitFlow<2>();
m_currentNode = nodes[0];
(*this)(_if.body);
nodes[0] = m_currentNode;
mergeFlow(nodes, nodes[1]);
}
void ControlFlowBuilder::operator()(yul::Switch const& _switch)
{
solAssert(m_currentNode && m_inlineAssembly, "");
visit(*_switch.expression);
auto beforeSwitch = m_currentNode;
auto nodes = splitFlow(_switch.cases.size());
for (size_t i = 0u; i < _switch.cases.size(); ++i)
{
m_currentNode = nodes[i];
(*this)(_switch.cases[i].body);
nodes[i] = m_currentNode;
}
mergeFlow(nodes);
bool hasDefault = util::contains_if(_switch.cases, [](yul::Case const& _case) { return !_case.value; });
if (!hasDefault)
connect(beforeSwitch, m_currentNode);
}
void ControlFlowBuilder::operator()(yul::ForLoop const& _forLoop)
{
solAssert(m_currentNode && m_inlineAssembly, "");
(*this)(_forLoop.pre);
auto condition = createLabelHere();
if (_forLoop.condition)
visit(*_forLoop.condition);
auto loopExpression = newLabel();
auto nodes = splitFlow<2>();
auto afterFor = nodes[1];
m_currentNode = nodes[0];
{
BreakContinueScope scope(*this, afterFor, loopExpression);
(*this)(_forLoop.body);
}
placeAndConnectLabel(loopExpression);
(*this)(_forLoop.post);
connect(m_currentNode, condition);
m_currentNode = afterFor;
}
void ControlFlowBuilder::operator()(yul::Break const&)
{
solAssert(m_currentNode && m_inlineAssembly, "");
solAssert(m_breakJump, "");
connect(m_currentNode, m_breakJump);
m_currentNode = newLabel();
}
void ControlFlowBuilder::operator()(yul::Continue const&)
{
solAssert(m_currentNode && m_inlineAssembly, "");
solAssert(m_continueJump, "");
connect(m_currentNode, m_continueJump);
m_currentNode = newLabel();
}
void ControlFlowBuilder::operator()(yul::Identifier const& _identifier)
{
solAssert(m_currentNode && m_inlineAssembly, "");
auto const& externalReferences = m_inlineAssembly->annotation().externalReferences;
if (externalReferences.count(&_identifier))
{
if (auto const* declaration = dynamic_cast<VariableDeclaration const*>(externalReferences.at(&_identifier).declaration))
m_currentNode->variableOccurrences.emplace_back(
*variableDeclaration,
VariableOccurrence::Kind::InlineAssembly,
&_inlineAssembly
*declaration,
VariableOccurrence::Kind::Access,
_identifier.location
);
}
return true;
}
void ControlFlowBuilder::operator()(yul::Assignment const& _assignment)
{
solAssert(m_currentNode && m_inlineAssembly, "");
visit(*_assignment.value);
auto const& externalReferences = m_inlineAssembly->annotation().externalReferences;
for (auto const& variable: _assignment.variableNames)
if (externalReferences.count(&variable))
if (auto const* declaration = dynamic_cast<VariableDeclaration const*>(externalReferences.at(&variable).declaration))
m_currentNode->variableOccurrences.emplace_back(
*declaration,
VariableOccurrence::Kind::Assignment,
variable.location
);
}
void ControlFlowBuilder::operator()(yul::FunctionCall const& _functionCall)
{
using namespace yul;
solAssert(m_currentNode && m_inlineAssembly, "");
yul::ASTWalker::operator()(_functionCall);
if (auto const *builtinFunction = m_inlineAssembly->dialect().builtin(_functionCall.functionName.name))
if (builtinFunction->controlFlowSideEffects.terminates)
{
if (builtinFunction->controlFlowSideEffects.reverts)
connect(m_currentNode, m_revertNode);
else
connect(m_currentNode, m_transactionReturnNode);
m_currentNode = newLabel();
}
}
void ControlFlowBuilder::operator()(yul::FunctionDefinition const&)
{
solAssert(m_currentNode && m_inlineAssembly, "");
// External references cannot be accessed from within functions, so we can ignore their control flow.
// TODO: we might still want to track if they always revert or return, though.
}
void ControlFlowBuilder::operator()(yul::Leave const&)
{
// This has to be implemented, if we ever decide to visit functions.
solUnimplementedAssert(false, "");
}
bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
@ -384,8 +528,7 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
m_currentNode->variableOccurrences.emplace_back(
_variableDeclaration,
VariableOccurrence::Kind::Declaration,
nullptr
VariableOccurrence::Kind::Declaration
);
// Handle declaration with immediate assignment.
@ -393,14 +536,13 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
m_currentNode->variableOccurrences.emplace_back(
_variableDeclaration,
VariableOccurrence::Kind::Assignment,
_variableDeclaration.value().get()
_variableDeclaration.value()->location()
);
// Function arguments are considered to be immediately assigned as well (they are "externally assigned").
else if (_variableDeclaration.isCallableOrCatchParameter() && !_variableDeclaration.isReturnParameter())
m_currentNode->variableOccurrences.emplace_back(
_variableDeclaration,
VariableOccurrence::Kind::Assignment,
nullptr
VariableOccurrence::Kind::Assignment
);
return true;
}
@ -434,7 +576,7 @@ bool ControlFlowBuilder::visit(VariableDeclarationStatement const& _variableDecl
m_currentNode->variableOccurrences.emplace_back(
*var,
VariableOccurrence::Kind::Assignment,
expression
expression ? std::make_optional(expression->location()) : std::optional<langutil::SourceLocation>{}
);
}
}
@ -452,7 +594,7 @@ bool ControlFlowBuilder::visit(Identifier const& _identifier)
static_cast<Expression const&>(_identifier).annotation().lValueRequested ?
VariableOccurrence::Kind::Assignment :
VariableOccurrence::Kind::Access,
&_identifier
_identifier.location()
);
return true;

View File

@ -20,6 +20,7 @@
#include <libsolidity/analysis/ControlFlowGraph.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTVisitor.h>
#include <libyul/optimiser/ASTWalker.h>
#include <array>
#include <memory>
@ -30,7 +31,7 @@ namespace solidity::frontend {
* Modifiers are not yet applied to the functions. This is done in a second
* step in the CFG class.
*/
class ControlFlowBuilder: private ASTConstVisitor
class ControlFlowBuilder: private ASTConstVisitor, private yul::ASTWalker
{
public:
static std::unique_ptr<FunctionFlow> createFunctionFlow(
@ -39,7 +40,10 @@ public:
);
private:
explicit ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow);
explicit ControlFlowBuilder(
CFG::NodeContainer& _nodeContainer,
FunctionFlow const& _functionFlow
);
// Visits for constructing the control flow.
bool visit(BinaryOperation const& _operation) override;
@ -62,6 +66,17 @@ private:
// Visits for filling variable occurrences.
bool visit(FunctionTypeName const& _functionTypeName) override;
bool visit(InlineAssembly const& _inlineAssembly) override;
void visit(yul::Statement const& _statement) override;
void operator()(yul::If const& _if) override;
void operator()(yul::Switch const& _switch) override;
void operator()(yul::ForLoop const& _for) override;
void operator()(yul::Break const&) override;
void operator()(yul::Continue const&) override;
void operator()(yul::Identifier const& _identifier) override;
void operator()(yul::Assignment const& _assignment) override;
void operator()(yul::FunctionCall const& _functionCall) override;
void operator()(yul::FunctionDefinition const& _functionDefinition) override;
void operator()(yul::Leave const& _leaveStatement) override;
bool visit(VariableDeclaration const& _variableDeclaration) override;
bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override;
bool visit(Identifier const& _identifier) override;
@ -70,6 +85,9 @@ protected:
bool visitNode(ASTNode const&) override;
private:
using ASTConstVisitor::visit;
using yul::ASTWalker::visit;
using yul::ASTWalker::operator();
/// Appends the control flow of @a _node to the current control flow.
void appendControlFlow(ASTNode const& _node);
@ -136,6 +154,7 @@ private:
CFGNode* m_currentNode = nullptr;
CFGNode* m_returnNode = nullptr;
CFGNode* m_revertNode = nullptr;
CFGNode* m_transactionReturnNode = nullptr;
/// The current jump destination of break Statements.
CFGNode* m_breakJump = nullptr;
@ -145,6 +164,8 @@ private:
CFGNode* m_placeholderEntry = nullptr;
CFGNode* m_placeholderExit = nullptr;
InlineAssembly const* m_inlineAssembly = nullptr;
/// Helper class that replaces the break and continue jump destinations for the
/// current scope and restores the originals at the end of the scope.
class BreakContinueScope

View File

@ -20,6 +20,7 @@
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTVisitor.h>
#include <liblangutil/ErrorReporter.h>
#include <liblangutil/EVMVersion.h>
#include <liblangutil/SourceLocation.h>
#include <map>
@ -33,8 +34,8 @@ namespace solidity::frontend
/**
* Occurrence of a variable in a block of control flow.
* Stores the declaration of the referenced variable, the
* kind of the occurrence and possibly the node at which
* it occurred.
* kind of the occurrence and possibly the source location
* at which it occurred.
*/
class VariableOccurrence
{
@ -47,7 +48,7 @@ public:
Assignment,
InlineAssembly
};
VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, ASTNode const* _occurrence):
VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, std::optional<langutil::SourceLocation> const& _occurrence = {}):
m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(_occurrence)
{
}
@ -57,8 +58,8 @@ public:
{
if (m_occurrence && _rhs.m_occurrence)
{
if (m_occurrence->id() < _rhs.m_occurrence->id()) return true;
if (_rhs.m_occurrence->id() < m_occurrence->id()) return false;
if (*m_occurrence < *_rhs.m_occurrence) return true;
if (*_rhs.m_occurrence < *m_occurrence) return false;
}
else if (_rhs.m_occurrence)
return true;
@ -74,14 +75,14 @@ public:
VariableDeclaration const& declaration() const { return m_declaration; }
Kind kind() const { return m_occurrenceKind; };
ASTNode const* occurrence() const { return m_occurrence; }
std::optional<langutil::SourceLocation> const& occurrence() const { return m_occurrence; }
private:
/// Declaration of the occurring variable.
VariableDeclaration const& m_declaration;
/// Kind of occurrence.
Kind m_occurrenceKind = Kind::Access;
/// AST node at which the variable occurred, if available (may be nullptr).
ASTNode const* m_occurrence = nullptr;
/// Source location at which the variable occurred, if available (may be nullptr).
std::optional<langutil::SourceLocation> m_occurrence;
};
/**
@ -119,6 +120,10 @@ struct FunctionFlow
/// This node is empty does not have any exits, but may have multiple entries
/// (e.g. all assert, require, revert and throw statements).
CFGNode* revert = nullptr;
/// Transaction return node. Destination node for inline assembly "return" calls.
/// This node is empty and does not have any exits, but may have multiple entries
/// (e.g. all inline assembly return calls).
CFGNode* transactionReturn = nullptr;
};
class CFG: private ASTConstVisitor
@ -140,7 +145,6 @@ public:
std::vector<std::unique_ptr<CFGNode>> m_nodes;
};
private:
langutil::ErrorReporter& m_errorReporter;
/// Node container.

View File

@ -67,6 +67,22 @@ void ReferencesResolver::endVisit(Block const& _block)
m_resolver.setScope(_block.scope());
}
bool ReferencesResolver::visit(TryCatchClause const& _tryCatchClause)
{
if (!m_resolveInsideCode)
return false;
m_resolver.setScope(&_tryCatchClause);
return true;
}
void ReferencesResolver::endVisit(TryCatchClause const& _tryCatchClause)
{
if (!m_resolveInsideCode)
return;
m_resolver.setScope(_tryCatchClause.scope());
}
bool ReferencesResolver::visit(ForStatement const& _for)
{
if (!m_resolveInsideCode)

View File

@ -70,6 +70,8 @@ private:
bool visit(Block const& _block) override;
void endVisit(Block const& _block) override;
bool visit(TryCatchClause const& _tryCatchClause) override;
void endVisit(TryCatchClause const& _tryCatchClause) override;
bool visit(ForStatement const& _for) override;
void endVisit(ForStatement const& _for) override;
void endVisit(VariableDeclarationStatement const& _varDeclStatement) override;

View File

@ -677,11 +677,21 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
m_errorReporter.typeError(_identifier.location, "The suffixes _offset and _slot can only be used on storage variables.");
return size_t(-1);
}
else if (_context != yul::IdentifierContext::RValue)
else if (_context == yul::IdentifierContext::LValue)
{
m_errorReporter.typeError(_identifier.location, "Storage variables cannot be assigned to.");
if (var->isStateVariable())
{
m_errorReporter.typeError(_identifier.location, "State variables cannot be assigned to - you have to use \"sstore()\".");
return size_t(-1);
}
else if (ref->second.isOffset)
{
m_errorReporter.typeError(_identifier.location, "Only _slot can be assigned to.");
return size_t(-1);
}
else
solAssert(ref->second.isSlot, "");
}
}
else if (!var->isConstant() && var->isStateVariable())
{
@ -1693,22 +1703,21 @@ void TypeChecker::typeCheckFunctionCall(
if (_functionType->kind() == FunctionType::Kind::Declaration)
{
if (
m_scope->derivesFrom(*_functionType->declaration().annotation().contract) &&
!dynamic_cast<FunctionDefinition const&>(_functionType->declaration()).isImplemented()
)
m_errorReporter.typeError(
_functionCall.location(),
"Cannot call unimplemented base function."
);
else
m_errorReporter.typeError(
_functionCall.location(),
"Cannot call function via contract type name."
);
return;
}
if (_functionType->kind() == FunctionType::Kind::Internal && _functionType->hasDeclaration())
if (auto const* functionDefinition = dynamic_cast<FunctionDefinition const*>(&_functionType->declaration()))
// functionDefinition->annotation().contract != m_scope ensures that this is a qualified access,
// e.g. ``A.f();`` instead of a simple function call like ``f();`` (the latter is valid for unimplemented
// functions).
if (functionDefinition->annotation().contract != m_scope && !functionDefinition->isImplemented())
m_errorReporter.typeError(
_functionCall.location(),
"Cannot call unimplemented base function."
);
// Check for unsupported use of bare static call
if (
@ -2302,6 +2311,10 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions)
else if (!expressionFunctionType->isPayable())
m_errorReporter.typeError(
_functionCallOptions.location(),
kind == FunctionType::Kind::Creation ?
"Cannot set option \"value\", since the constructor of " +
expressionFunctionType->returnParameterTypes().front()->toString() +
" is not payable." :
"Cannot set option \"value\" on a non-payable function type."
);
else
@ -2512,12 +2525,24 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
annotation.type = possibleMembers.front().type;
if (auto funType = dynamic_cast<FunctionType const*>(annotation.type))
{
solAssert(
!funType->bound() || exprType->isImplicitlyConvertibleTo(*funType->selfType()),
"Function \"" + memberName + "\" cannot be called on an object of type " +
exprType->toString() + " (expected " + funType->selfType()->toString() + ")."
);
if (
dynamic_cast<FunctionType const*>(exprType) &&
!annotation.referencedDeclaration &&
(memberName == "value" || memberName == "gas")
)
m_errorReporter.warning(
_memberAccess.location(),
"Using \"." + memberName + "(...)\" is deprecated. Use \"{" + memberName + ": ...}\" instead."
);
}
if (auto const* structType = dynamic_cast<StructType const*>(exprType))
annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData);
else if (exprType->category() == Type::Category::Array)

View File

@ -390,7 +390,7 @@ DeclarationAnnotation& Declaration::annotation() const
bool VariableDeclaration::isLValue() const
{
// Constant declared variables are Read-Only
if (m_isConstant)
if (isConstant())
return false;
// External function arguments of reference type are Read-Only
if (isExternalCallableParameter() && dynamic_cast<ReferenceType const*>(type()))

View File

@ -814,6 +814,7 @@ class VariableDeclaration: public Declaration
{
public:
enum Location { Unspecified, Storage, Memory, CallData };
enum class Constantness { Mutable, Immutable, Constant };
VariableDeclaration(
int64_t _id,
@ -824,7 +825,7 @@ public:
Visibility _visibility,
bool _isStateVar = false,
bool _isIndexed = false,
bool _isConstant = false,
Constantness _constantness = Constantness::Mutable,
ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
Location _referenceLocation = Location::Unspecified
):
@ -833,7 +834,7 @@ public:
m_value(_value),
m_isStateVariable(_isStateVar),
m_isIndexed(_isIndexed),
m_isConstant(_isConstant),
m_constantness(_constantness),
m_overrides(_overrides),
m_location(_referenceLocation) {}
@ -877,7 +878,7 @@ public:
bool hasReferenceOrMappingType() const;
bool isStateVariable() const { return m_isStateVariable; }
bool isIndexed() const { return m_isIndexed; }
bool isConstant() const { return m_isConstant; }
bool isConstant() const { return m_constantness == Constantness::Constant; }
ASTPointer<OverrideSpecifier> const& overrides() const { return m_overrides; }
Location referenceLocation() const { return m_location; }
/// @returns a set of allowed storage locations for the variable.
@ -904,7 +905,8 @@ private:
ASTPointer<Expression> m_value;
bool m_isStateVariable = false; ///< Whether or not this is a contract state variable
bool m_isIndexed = false; ///< Whether this is an indexed variable (used by events).
bool m_isConstant = false; ///< Whether the variable is a compile-time constant.
/// Whether the variable is "constant", "immutable" or non-marked (mutable).
Constantness m_constantness = Constantness::Mutable;
ASTPointer<OverrideSpecifier> m_overrides; ///< Contains the override specifier node
Location m_location = Location::Unspecified; ///< Location of the variable if it is of reference type.
};

View File

@ -411,6 +411,12 @@ ASTPointer<VariableDeclaration> ASTJsonImporter::createVariableDeclaration(Json:
{
astAssert(_node["name"].isString(), "Expected 'name' to be a string!");
VariableDeclaration::Constantness constantness{};
if (memberAsBool(_node, "constant"))
constantness = VariableDeclaration::Constantness::Constant;
else
constantness = VariableDeclaration::Constantness::Mutable;
return createASTNode<VariableDeclaration>(
_node,
nullOrCast<TypeName>(member(_node, "typeName")),
@ -419,7 +425,7 @@ ASTPointer<VariableDeclaration> ASTJsonImporter::createVariableDeclaration(Json:
visibility(_node),
memberAsBool(_node, "stateVariable"),
_node.isMember("indexed") ? memberAsBool(_node, "indexed") : false,
memberAsBool(_node, "constant"),
constantness,
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
location(_node)
);

View File

@ -104,6 +104,8 @@ yul::Statement AsmJsonImporter::createStatement(Json::Value const& _node)
return createContinue(_node);
else if (nodeType == "Leave")
return createLeave(_node);
else if (nodeType == "Block")
return createBlock(_node);
else
astAssert(false, "Invalid nodeType as statement");
}
@ -158,10 +160,9 @@ yul::Literal AsmJsonImporter::createLiteral(Json::Value const& _node)
lit.value = YulString{member(_node, "value").asString()};
lit.type= YulString{member(_node, "type").asString()};
langutil::Scanner scanner{langutil::CharStream(lit.value.str(), "")};
if (kind == "number")
{
langutil::Scanner scanner{langutil::CharStream(lit.value.str(), "")};
lit.kind = yul::LiteralKind::Number;
astAssert(
scanner.currentToken() == Token::Number,
@ -170,6 +171,7 @@ yul::Literal AsmJsonImporter::createLiteral(Json::Value const& _node)
}
else if (kind == "bool")
{
langutil::Scanner scanner{langutil::CharStream(lit.value.str(), "")};
lit.kind = yul::LiteralKind::Boolean;
astAssert(
scanner.currentToken() == Token::TrueLiteral ||
@ -180,7 +182,10 @@ yul::Literal AsmJsonImporter::createLiteral(Json::Value const& _node)
else if (kind == "string")
{
lit.kind = yul::LiteralKind::String;
astAssert(scanner.currentToken() == Token::StringLiteral, "Expected string literal!");
astAssert(
lit.value.str().size() <= 32,
"String literal too long (" + to_string(lit.value.str().size()) + " > 32)"
);
}
else
solAssert(false, "unknown type of literal");
@ -268,7 +273,11 @@ yul::If AsmJsonImporter::createIf(Json::Value const& _node)
yul::Case AsmJsonImporter::createCase(Json::Value const& _node)
{
auto caseStatement = createAsmNode<yul::Case>(_node);
caseStatement.value = member(_node, "value").asString() == "default" ? nullptr : make_unique<yul::Literal>(createLiteral(member(_node, "value")));
auto const& value = member(_node, "value");
if (value.isString())
astAssert(value.asString() == "default", "Expected default case");
else
caseStatement.value = make_unique<yul::Literal>(createLiteral(value));
caseStatement.body = createBlock(member(_node, "body"));
return caseStatement;
}
@ -276,7 +285,7 @@ yul::Case AsmJsonImporter::createCase(Json::Value const& _node)
yul::Switch AsmJsonImporter::createSwitch(Json::Value const& _node)
{
auto switchStatement = createAsmNode<yul::Switch>(_node);
switchStatement.expression = make_unique<yul::Expression>(createExpression(member(_node, "value")));
switchStatement.expression = make_unique<yul::Expression>(createExpression(member(_node, "expression")));
for (auto const& var: member(_node, "cases"))
switchStatement.cases.emplace_back(createCase(var));
return switchStatement;

View File

@ -2924,7 +2924,10 @@ vector<tuple<string, TypePointer>> FunctionType::makeStackItems() const
{
case Kind::External:
case Kind::DelegateCall:
slots = {make_tuple("address", TypeProvider::address()), make_tuple("functionIdentifier", TypeProvider::fixedBytes(4))};
slots = {
make_tuple("address", TypeProvider::address()),
make_tuple("functionIdentifier", TypeProvider::uint(32))
};
break;
case Kind::BareCall:
case Kind::BareCallCode:
@ -3471,7 +3474,15 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current
continue;
if (!contract.isLibrary() && inDerivingScope && declaration->isVisibleInDerivedContracts())
{
if (
auto const* functionDefinition = dynamic_cast<FunctionDefinition const*>(declaration);
functionDefinition && !functionDefinition->isImplemented()
)
members.emplace_back(declaration->name(), declaration->typeViaContractName(), declaration);
else
members.emplace_back(declaration->name(), declaration->type(), declaration);
}
else if (
(contract.isLibrary() && declaration->isVisibleAsLibraryMember()) ||
declaration->isVisibleViaContractTypeAccess()

View File

@ -55,7 +55,7 @@ string ABIFunctions::tupleEncoder(
functionName += t->identifier() + "_";
functionName += options.toFunctionNameSuffix();
return createExternallyUsedFunction(functionName, [&]() {
return createFunction(functionName, [&]() {
// Note that the values are in reverse due to the difference in calling semantics.
Whiskers templ(R"(
function <functionName>(headStart <valueParams>) -> tail {
@ -121,7 +121,7 @@ string ABIFunctions::tupleEncoderPacked(
functionName += t->identifier() + "_";
functionName += options.toFunctionNameSuffix();
return createExternallyUsedFunction(functionName, [&]() {
return createFunction(functionName, [&]() {
solAssert(!_givenTypes.empty(), "");
// Note that the values are in reverse due to the difference in calling semantics.
@ -173,7 +173,7 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
if (_fromMemory)
functionName += "_fromMemory";
return createExternallyUsedFunction(functionName, [&]() {
return createFunction(functionName, [&]() {
TypePointers decodingTypes;
for (auto const& t: _types)
decodingTypes.emplace_back(t->decodingType());
@ -240,13 +240,6 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
});
}
pair<string, set<string>> ABIFunctions::requestedFunctions()
{
std::set<string> empty;
swap(empty, m_externallyUsedFunctions);
return make_pair(m_functionCollector->requestedFunctions(), std::move(empty));
}
string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const
{
string suffix;
@ -1499,14 +1492,7 @@ string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type,
string ABIFunctions::createFunction(string const& _name, function<string ()> const& _creator)
{
return m_functionCollector->createFunction(_name, _creator);
}
string ABIFunctions::createExternallyUsedFunction(string const& _name, function<string ()> const& _creator)
{
string name = createFunction(_name, _creator);
m_externallyUsedFunctions.insert(name);
return name;
return m_functionCollector.createFunction(_name, _creator);
}
size_t ABIFunctions::headSize(TypePointers const& _targetTypes)

View File

@ -31,7 +31,6 @@
#include <functional>
#include <map>
#include <set>
#include <vector>
namespace solidity::frontend
@ -58,11 +57,11 @@ public:
explicit ABIFunctions(
langutil::EVMVersion _evmVersion,
RevertStrings _revertStrings,
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector = std::make_shared<MultiUseYulFunctionCollector>()
MultiUseYulFunctionCollector& _functionCollector
):
m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings),
m_functionCollector(std::move(_functionCollector)),
m_functionCollector(_functionCollector),
m_utils(_evmVersion, m_revertStrings, m_functionCollector)
{}
@ -104,12 +103,6 @@ public:
/// stack slot, it takes exactly that number of values.
std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false);
/// @returns concatenation of all generated functions and a set of the
/// externally used functions.
/// Clears the internal list, i.e. calling it again will result in an
/// empty return value.
std::pair<std::string, std::set<std::string>> requestedFunctions();
private:
struct EncodingOptions
{
@ -239,11 +232,6 @@ private:
/// cases.
std::string createFunction(std::string const& _name, std::function<std::string()> const& _creator);
/// Helper function that uses @a _creator to create a function and add it to
/// @a m_requestedFunctions if it has not been created yet and returns @a _name in both
/// cases. Also adds it to the list of externally used functions.
std::string createExternallyUsedFunction(std::string const& _name, std::function<std::string()> const& _creator);
/// @returns the size of the static part of the encoding of the given types.
static size_t headSize(TypePointers const& _targetTypes);
@ -259,8 +247,7 @@ private:
langutil::EVMVersion m_evmVersion;
RevertStrings const m_revertStrings;
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
std::set<std::string> m_externallyUsedFunctions;
MultiUseYulFunctionCollector& m_functionCollector;
YulUtilFunctions m_utils;
};

View File

@ -94,6 +94,20 @@ void CompilerContext::callLowLevelFunction(
*this << retTag.tag();
}
void CompilerContext::callYulFunction(
string const& _name,
unsigned _inArgs,
unsigned _outArgs
)
{
m_externallyUsedYulFunctions.insert(_name);
auto const retTag = pushNewTag();
CompilerUtils(*this).moveIntoStack(_inArgs);
appendJumpTo(namedTag(_name));
adjustStackOffset(int(_outArgs) - 1 - _inArgs);
*this << retTag.tag();
}
evmasm::AssemblyItem CompilerContext::lowLevelFunctionTag(
string const& _name,
unsigned _inArgs,
@ -133,6 +147,13 @@ void CompilerContext::appendMissingLowLevelFunctions()
}
}
pair<string, set<string>> CompilerContext::requestedYulFunctions()
{
set<string> empty;
swap(empty, m_externallyUsedYulFunctions);
return {m_yulFunctionCollector.requestedFunctions(), std::move(empty)};
}
void CompilerContext::addVariable(
VariableDeclaration const& _declaration,
unsigned _offsetToCurrent

View File

@ -65,7 +65,8 @@ public:
m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings),
m_runtimeContext(_runtimeContext),
m_abiFunctions(m_evmVersion, m_revertStrings)
m_abiFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector),
m_yulUtilFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector)
{
if (m_runtimeContext)
m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data());
@ -131,6 +132,14 @@ public:
unsigned _outArgs,
std::function<void(CompilerContext&)> const& _generator
);
/// Appends a call to a yul function and registers the function as externally used.
void callYulFunction(
std::string const& _name,
unsigned _inArgs,
unsigned _outArgs
);
/// Returns the tag of the named low-level function and inserts the generator into the
/// list of low-level-functions to be generated, unless it already exists.
/// Note that the generator should not assume that objects are still alive when it is called,
@ -144,6 +153,12 @@ public:
/// Generates the code for missing low-level functions, i.e. calls the generators passed above.
void appendMissingLowLevelFunctions();
ABIFunctions& abiFunctions() { return m_abiFunctions; }
YulUtilFunctions& utilFunctions() { return m_yulUtilFunctions; }
/// @returns concatenation of all generated functions and a set of the
/// externally used functions.
/// Clears the internal list, i.e. calling it again will result in an
/// empty return value.
std::pair<std::string, std::set<std::string>> requestedYulFunctions();
ModifierDefinition const& resolveVirtualFunctionModifier(ModifierDefinition const& _modifier) const;
/// Returns the distance of the given local variable from the bottom of the stack (of the current function).
@ -355,8 +370,14 @@ private:
size_t m_runtimeSub = -1;
/// An index of low-level function labels by name.
std::map<std::string, evmasm::AssemblyItem> m_lowLevelFunctions;
/// Collector for yul functions.
MultiUseYulFunctionCollector m_yulFunctionCollector;
/// Set of externally used yul functions.
std::set<std::string> m_externallyUsedYulFunctions;
/// Container for ABI functions to be generated.
ABIFunctions m_abiFunctions;
/// Container for Yul Util functions to be generated.
YulUtilFunctions m_yulUtilFunctions;
/// The queue of low-level functions to generate.
std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue;
};

View File

@ -121,57 +121,13 @@ void CompilerUtils::returnDataToArray()
void CompilerUtils::accessCalldataTail(Type const& _type)
{
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
solAssert(_type.isDynamicallyEncoded(), "");
unsigned int tailSize = _type.calldataEncodedTailSize();
solAssert(tailSize > 1, "");
// returns the absolute offset of the tail in "base_ref"
m_context.appendInlineAssembly(Whiskers(R"({
let rel_offset_of_tail := calldataload(ptr_to_tail)
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { <revertString> }
base_ref := add(base_ref, rel_offset_of_tail)
})")
("neededLength", toCompactHexWithPrefix(tailSize))
("revertString", m_context.revertReasonIfDebug("Invalid calldata tail offset"))
.render(), {"base_ref", "ptr_to_tail"});
// stack layout: <absolute_offset_of_tail> <garbage>
if (!_type.isDynamicallySized())
{
m_context << Instruction::POP;
// stack layout: <absolute_offset_of_tail>
solAssert(
_type.category() == Type::Category::Struct ||
_type.category() == Type::Category::Array,
"Invalid dynamically encoded base type on tail access."
m_context << Instruction::SWAP1;
m_context.callYulFunction(
m_context.utilFunctions().accessCalldataTailFunction(_type),
2,
_type.isDynamicallySized() ? 2 : 1
);
}
else
{
auto const* arrayType = dynamic_cast<ArrayType const*>(&_type);
solAssert(!!arrayType, "Invalid dynamically sized type.");
unsigned int calldataStride = arrayType->calldataStride();
solAssert(calldataStride > 0, "");
// returns the absolute offset of the tail in "base_ref"
// and the length of the tail in "length"
m_context.appendInlineAssembly(
Whiskers(R"({
length := calldataload(base_ref)
base_ref := add(base_ref, 0x20)
if gt(length, 0xffffffffffffffff) { <revertString> }
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
})")
("calldataStride", toCompactHexWithPrefix(calldataStride))
("revertString", m_context.revertReasonIfDebug("Invalid calldata tail length"))
.render(),
{"base_ref", "length"}
);
// stack layout: <absolute_offset_of_tail> <length>
}
}
unsigned CompilerUtils::loadFromMemory(
unsigned _offset,
@ -539,6 +495,10 @@ void CompilerUtils::encodeToMemory(
if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace)
{
// copy tail pointer (=mem_end - mem_start) to memory
solAssert(
(2 + dynPointers) <= 16,
"Stack too deep(" + to_string(2 + dynPointers) + "), try using fewer variables."
);
m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2;
m_context << Instruction::SUB;
m_context << dupInstruction(2 + dynPointers - thisDynPointer);
@ -595,31 +555,21 @@ void CompilerUtils::abiEncodeV2(
// stack: <$value0> <$value1> ... <$value(n-1)> <$headStart>
auto ret = m_context.pushNewTag();
moveIntoStack(sizeOnStack(_givenTypes) + 1);
string encoderName =
_padToWordBoundaries ?
m_context.abiFunctions().tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes) :
m_context.abiFunctions().tupleEncoderPacked(_givenTypes, _targetTypes);
m_context.appendJumpTo(m_context.namedTag(encoderName));
m_context.adjustStackOffset(-int(sizeOnStack(_givenTypes)) - 1);
m_context << ret.tag();
m_context.callYulFunction(encoderName, sizeOnStack(_givenTypes) + 1, 1);
}
void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory)
{
// stack: <source_offset> <length> [stack top]
auto ret = m_context.pushNewTag();
moveIntoStack(2);
// stack: <return tag> <source_offset> <length> [stack top]
m_context << Instruction::DUP2 << Instruction::ADD;
m_context << Instruction::SWAP1;
// stack: <return tag> <end> <start>
// stack: <end> <start>
string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory);
m_context.appendJumpTo(m_context.namedTag(decoderName));
m_context.adjustStackOffset(int(sizeOnStack(_parameterTypes)) - 3);
m_context << ret.tag();
m_context.callYulFunction(decoderName, 2, sizeOnStack(_parameterTypes));
}
void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)

View File

@ -781,7 +781,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
else
{
// lvalue context
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
solAssert(!ref->second.isOffset, "");
auto variable = dynamic_cast<VariableDeclaration const*>(decl);
solAssert(
!!variable && m_context.isLocalVariable(variable),
@ -1267,12 +1267,12 @@ void ContractCompiler::appendMissingFunctions()
solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?");
}
m_context.appendMissingLowLevelFunctions();
auto abiFunctions = m_context.abiFunctions().requestedFunctions();
if (!abiFunctions.first.empty())
auto [yulFunctions, externallyUsedYulFunctions] = m_context.requestedYulFunctions();
if (!yulFunctions.empty())
m_context.appendInlineAssembly(
"{" + move(abiFunctions.first) + "}",
"{" + move(yulFunctions) + "}",
{},
abiFunctions.second,
externallyUsedYulFunctions,
true,
m_optimiserSettings
);

View File

@ -39,7 +39,7 @@ using namespace solidity::frontend;
string YulUtilFunctions::combineExternalFunctionIdFunction()
{
string functionName = "combine_external_function_id";
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(addr, selector) -> combined {
combined := <shl64>(or(<shl32>(addr), and(selector, 0xffffffff)))
@ -55,7 +55,7 @@ string YulUtilFunctions::combineExternalFunctionIdFunction()
string YulUtilFunctions::splitExternalFunctionIdFunction()
{
string functionName = "split_external_function_id";
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(combined) -> addr, selector {
combined := <shr64>(combined)
@ -73,7 +73,7 @@ string YulUtilFunctions::splitExternalFunctionIdFunction()
string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata)
{
string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory";
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
if (_fromCalldata)
{
return Whiskers(R"(
@ -116,7 +116,7 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess
solAssert(!_assert || !_messageType, "Asserts can't have messages!");
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
if (!_messageType)
return Whiskers(R"(
function <functionName>(condition) {
@ -166,7 +166,7 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess
string YulUtilFunctions::leftAlignFunction(Type const& _type)
{
string functionName = string("leftAlign_") + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(value) -> aligned {
<body>
@ -228,7 +228,7 @@ string YulUtilFunctions::shiftLeftFunction(size_t _numBits)
solAssert(_numBits < 256, "");
string functionName = "shift_left_" + to_string(_numBits);
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value) -> newValue {
@ -251,7 +251,7 @@ string YulUtilFunctions::shiftLeftFunction(size_t _numBits)
string YulUtilFunctions::shiftLeftFunctionDynamic()
{
string functionName = "shift_left_dynamic";
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(bits, value) -> newValue {
@ -277,7 +277,7 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits)
// the opcodes SAR and SDIV behave differently with regards to rounding!
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned";
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value) -> newValue {
@ -303,7 +303,7 @@ string YulUtilFunctions::shiftRightFunctionDynamic()
// the opcodes SAR and SDIV behave differently with regards to rounding!
string const functionName = "shift_right_unsigned_dynamic";
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(bits, value) -> newValue {
@ -328,7 +328,7 @@ string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shift
size_t numBits = _numBytes * 8;
size_t shiftBits = _shiftBytes * 8;
string functionName = "update_byte_slice_" + to_string(_numBytes) + "_shift_" + to_string(_shiftBytes);
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value, toInsert) -> result {
@ -350,7 +350,7 @@ string YulUtilFunctions::updateByteSliceFunctionDynamic(size_t _numBytes)
solAssert(_numBytes <= 32, "");
size_t numBits = _numBytes * 8;
string functionName = "update_byte_slice_dynamic" + to_string(_numBytes);
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value, shiftBytes, toInsert) -> result {
@ -371,7 +371,7 @@ string YulUtilFunctions::updateByteSliceFunctionDynamic(size_t _numBytes)
string YulUtilFunctions::roundUpFunction()
{
string functionName = "round_up_to_mul_of_32";
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value) -> result {
@ -389,7 +389,7 @@ string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
// TODO: Consider to add a special case for unsigned 256-bit integers
// and use the following instead:
// sum := add(x, y) if lt(sum, x) { revert(0, 0) }
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(x, y) -> sum {
@ -416,7 +416,7 @@ string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
{
string functionName = "checked_mul_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return
// Multiplication by zero could be treated separately and directly return zero.
Whiskers(R"(
@ -448,7 +448,7 @@ string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
{
string functionName = "checked_div_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(x, y) -> r {
@ -473,7 +473,7 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type)
{
string functionName = "checked_mod_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(x, y) -> r {
@ -490,7 +490,7 @@ string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type)
string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
{
string functionName = "checked_sub_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
return m_functionCollector.createFunction(functionName, [&] {
return
Whiskers(R"(
function <functionName>(x, y) -> diff {
@ -516,7 +516,7 @@ string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
{
string functionName = "array_length_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers w(R"(
function <functionName>(value) -> length {
<?dynamic>
@ -564,7 +564,7 @@ std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type)
solUnimplementedAssert(_type.baseType()->storageSize() == 1, "");
string functionName = "resize_array_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array, newLen) {
if gt(newLen, <maxArrayLength>) {
@ -604,7 +604,7 @@ string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type)
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
string functionName = "array_pop_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array) {
let oldLen := <fetchLength>(array)
@ -632,7 +632,7 @@ string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
string functionName = "array_push_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array, value) {
let oldLen := <fetchLength>(array)
@ -659,7 +659,7 @@ string YulUtilFunctions::storageArrayPushZeroFunction(ArrayType const& _type)
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
string functionName = "array_push_zero_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array) -> slot, offset {
let oldLen := <fetchLength>(array)
@ -684,7 +684,7 @@ string YulUtilFunctions::clearStorageRangeFunction(Type const& _type)
solAssert(_type.storageBytes() >= 32, "Expected smaller value for storage bytes");
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(start, end) {
for {} lt(start, end) { start := add(start, <increment>) }
@ -715,7 +715,7 @@ string YulUtilFunctions::clearStorageArrayFunction(ArrayType const& _type)
string functionName = "clear_storage_array_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(slot) {
<?dynamic>
@ -745,7 +745,7 @@ string YulUtilFunctions::clearStorageArrayFunction(ArrayType const& _type)
string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
{
string functionName = "array_convert_length_to_size_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
Type const& baseType = *_type.baseType();
switch (_type.location())
@ -798,7 +798,7 @@ string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
{
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
string functionName = "array_allocation_size_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers w(R"(
function <functionName>(length) -> size {
// Make sure we can allocate memory without overflow
@ -825,7 +825,7 @@ string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
{
string functionName = "array_dataslot_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
// No special processing for calldata arrays, because they are stored as
// offset of the data area and length on the stack, so the offset already
// points to the data area.
@ -858,7 +858,7 @@ 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 m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(array, index) -> slot, offset {
if iszero(lt(index, <arrayLen>(array))) {
@ -886,7 +886,7 @@ string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type)
{
string functionName = "memory_array_index_access_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(baseRef, index) -> addr {
if iszero(lt(index, <arrayLen>(baseRef))) {
@ -912,7 +912,7 @@ string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& _type
{
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
string functionName = "calldata_array_index_access_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(base_ref<?dynamicallySized>, length</dynamicallySized>, index) -> addr<?dynamicallySizedBase>, len</dynamicallySizedBase> {
if iszero(lt(index, <?dynamicallySized>length<!dynamicallySized><arrayLen></dynamicallySized>)) { invalid() }
@ -938,17 +938,17 @@ string YulUtilFunctions::accessCalldataTailFunction(Type const& _type)
solAssert(_type.isDynamicallyEncoded(), "");
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
string functionName = "access_calldata_tail_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(base_ref, ptr_to_tail) -> addr<?dynamicallySized>, length</dynamicallySized> {
let rel_offset_of_tail := calldataload(ptr_to_tail)
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) }
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { <invalidCalldataTailOffset> }
addr := add(base_ref, rel_offset_of_tail)
<?dynamicallySized>
length := calldataload(addr)
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
if gt(length, 0xffffffffffffffff) { <invalidCalldataTailLength> }
addr := add(addr, 32)
if sgt(addr, sub(calldatasize(), mul(length, <calldataStride>))) { <shortCalldataTail> }
</dynamicallySized>
}
)")
@ -956,6 +956,9 @@ string YulUtilFunctions::accessCalldataTailFunction(Type const& _type)
("dynamicallySized", _type.isDynamicallySized())
("neededLength", toCompactHexWithPrefix(_type.calldataEncodedTailSize()))
("calldataStride", toCompactHexWithPrefix(_type.isDynamicallySized() ? dynamic_cast<ArrayType const&>(_type).calldataStride() : 0))
("invalidCalldataTailOffset", revertReasonIfDebug("Invalid calldata tail offset"))
("invalidCalldataTailLength", revertReasonIfDebug("Invalid calldata tail length"))
("shortCalldataTail", revertReasonIfDebug("Calldata tail too short"))
.render();
});
}
@ -966,7 +969,7 @@ string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
if (_type.dataStoredIn(DataLocation::Storage))
solAssert(_type.baseType()->storageBytes() > 16, "");
string functionName = "array_nextElement_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(ptr) -> next {
next := add(ptr, <advance>)
@ -1002,7 +1005,7 @@ string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingT
solAssert(_keyType.sizeOnStack() <= 1, "");
string functionName = "mapping_index_access_" + _mappingType.identifier() + "_of_" + _keyType.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
if (_mappingType.keyType()->isDynamicallySized())
return Whiskers(R"(
function <functionName>(slot <comma> <key>) -> dataSlot {
@ -1050,7 +1053,7 @@ string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool
to_string(_offset) +
"_" +
_type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
return m_functionCollector.createFunction(functionName, [&] {
solAssert(_type.sizeOnStack() == 1, "");
return Whiskers(R"(
function <functionName>(slot) -> value {
@ -1071,7 +1074,7 @@ string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFu
string(_splitFunctionTypes ? "split_" : "") +
"_" +
_type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
return m_functionCollector.createFunction(functionName, [&] {
solAssert(_type.sizeOnStack() == 1, "");
return Whiskers(R"(
function <functionName>(slot, offset) -> value {
@ -1101,7 +1104,7 @@ string YulUtilFunctions::updateStorageValueFunction(Type const& _type, std::opti
(_offset.has_value() ? ("offset_" + to_string(*_offset)) : "") +
_type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
return m_functionCollector.createFunction(functionName, [&] {
if (_type.isValueType())
{
solAssert(_type.storageBytes() <= 32, "Invalid storage bytes size.");
@ -1141,7 +1144,7 @@ string YulUtilFunctions::writeToMemoryFunction(Type const& _type)
string("write_to_memory_") +
_type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
return m_functionCollector.createFunction(functionName, [&] {
solAssert(!dynamic_cast<StringLiteralType const*>(&_type), "");
if (auto ref = dynamic_cast<ReferenceType const*>(&_type))
{
@ -1201,7 +1204,7 @@ string YulUtilFunctions::extractFromStorageValueDynamic(Type const& _type, bool
"extract_from_storage_value_dynamic" +
string(_splitFunctionTypes ? "split_" : "") +
_type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
return m_functionCollector.createFunction(functionName, [&] {
return Whiskers(R"(
function <functionName>(slot_value, offset) -> value {
value := <cleanupStorage>(<shr>(mul(offset, 8), slot_value))
@ -1224,7 +1227,7 @@ string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offs
"offset_" +
to_string(_offset) +
_type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
return m_functionCollector.createFunction(functionName, [&] {
return Whiskers(R"(
function <functionName>(slot_value) -> value {
value := <cleanupStorage>(<shr>(slot_value))
@ -1243,7 +1246,7 @@ string YulUtilFunctions::cleanupFromStorageFunction(Type const& _type, bool _spl
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
return m_functionCollector.createFunction(functionName, [&] {
Whiskers templ(R"(
function <functionName>(value) -> cleaned {
cleaned := <cleaned>
@ -1275,7 +1278,7 @@ string YulUtilFunctions::prepareStoreFunction(Type const& _type)
solUnimplementedAssert(_type.category() != Type::Category::Function, "");
string functionName = "prepare_store_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(value) -> ret {
ret := <actualPrepare>
@ -1293,7 +1296,7 @@ string YulUtilFunctions::prepareStoreFunction(Type const& _type)
string YulUtilFunctions::allocationFunction()
{
string functionName = "allocateMemory";
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(size) -> memPtr {
memPtr := mload(<freeMemoryPointer>)
@ -1314,7 +1317,7 @@ string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type)
solUnimplementedAssert(!_type.isByteArray(), "");
string functionName = "allocate_memory_array_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(length) -> memPtr {
memPtr := <alloc>(<allocSize>(length))
@ -1333,6 +1336,35 @@ string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type)
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
{
if (_from.category() == Type::Category::Function)
{
solAssert(_to.category() == Type::Category::Function, "");
FunctionType const& fromType = dynamic_cast<FunctionType const&>(_from);
FunctionType const& targetType = dynamic_cast<FunctionType const&>(_to);
solAssert(
fromType.isImplicitlyConvertibleTo(targetType) &&
fromType.sizeOnStack() == targetType.sizeOnStack() &&
(fromType.kind() == FunctionType::Kind::Internal || fromType.kind() == FunctionType::Kind::External) &&
fromType.kind() == targetType.kind(),
"Invalid function type conversion requested."
);
string const functionName =
"convert_" +
_from.identifier() +
"_to_" +
_to.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(addr, functionId) -> outAddr, outFunctionId {
outAddr := addr
outFunctionId := functionId
}
)")
("functionName", functionName)
.render();
});
}
if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1)
return conversionFunctionSpecial(_from, _to);
@ -1341,7 +1373,7 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
_from.identifier() +
"_to_" +
_to.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(value) -> converted {
<body>
@ -1440,22 +1472,34 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
break;
case Type::Category::Array:
{
bool equal = _from == _to;
if (!equal)
if (_from == _to)
body = "converted := value";
else
{
ArrayType const& from = dynamic_cast<decltype(from)>(_from);
ArrayType const& to = dynamic_cast<decltype(to)>(_to);
if (*from.mobileType() == *to.mobileType())
equal = true;
}
if (equal)
switch (to.location())
{
case DataLocation::Storage:
// Other cases are done explicitly in LValue::storeValue, and only possible by assignment.
solAssert(
(to.isPointer() || (from.isByteArray() && to.isByteArray())) &&
from.location() == DataLocation::Storage,
"Invalid conversion to storage type."
);
body = "converted := value";
else
solUnimplementedAssert(false, "Array conversion not implemented.");
break;
case DataLocation::Memory:
// Copy the array to a free position in memory, unless it is already in memory.
solUnimplementedAssert(from.location() == DataLocation::Memory, "Not implemented yet.");
body = "converted := value";
break;
case DataLocation::CallData:
solUnimplemented("Conversion of calldata types not yet implemented.");
break;
}
}
break;
}
case Type::Category::Struct:
@ -1519,7 +1563,7 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
string YulUtilFunctions::cleanupFunction(Type const& _type)
{
string functionName = string("cleanup_") + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(value) -> cleaned {
<body>
@ -1606,7 +1650,7 @@ string YulUtilFunctions::cleanupFunction(Type const& _type)
string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFailure)
{
string functionName = string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(value) {
if iszero(<condition>) { <failure> }
@ -1667,7 +1711,7 @@ string YulUtilFunctions::packedHashFunction(
size_t sizeOnStack = 0;
for (Type const* t: _givenTypes)
sizeOnStack += t->sizeOnStack();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(<variables>) -> hash {
let pos := mload(<freeMemoryPointer>)
@ -1688,7 +1732,7 @@ string YulUtilFunctions::forwardingRevertFunction()
{
bool forward = m_evmVersion.supportsReturndata();
string functionName = "revert_forward_" + to_string(forward);
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
if (forward)
return Whiskers(R"(
function <functionName>() {
@ -1715,7 +1759,7 @@ std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type)
string const functionName = "decrement_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
u256 minintval;
// Smallest admissible value to decrement
@ -1743,7 +1787,7 @@ std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type)
string const functionName = "increment_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
u256 maxintval;
// Biggest admissible value to increment
@ -1774,7 +1818,7 @@ string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type)
u256 const minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1;
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(_value) -> ret {
if slt(_value, <minval>) { revert(0,0) }
@ -1794,7 +1838,7 @@ string YulUtilFunctions::zeroValueFunction(Type const& _type)
string const functionName = "zero_value_for_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>() -> ret {
<body>
@ -1810,7 +1854,7 @@ string YulUtilFunctions::storageSetToZeroFunction(Type const& _type)
{
string const functionName = "storage_set_to_zero_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
if (_type.isValueType())
return Whiskers(R"(
function <functionName>(slot, offset) {
@ -1842,7 +1886,7 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const
_from.identifier() +
"_to_" +
_to.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return m_functionCollector.createFunction(functionName, [&]() {
if (
auto fromTuple = dynamic_cast<TupleType const*>(&_from), toTuple = dynamic_cast<TupleType const*>(&_to);
fromTuple && toTuple && fromTuple->components().size() == toTuple->components().size()
@ -1950,7 +1994,7 @@ string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromC
if (_fromCalldata)
solAssert(!_type.isDynamicallyEncoded(), "");
return m_functionCollector->createFunction(functionName, [&] {
return m_functionCollector.createFunction(functionName, [&] {
if (auto refType = dynamic_cast<ReferenceType const*>(&_type))
{
solAssert(refType->sizeOnStack() == 1, "");

View File

@ -47,11 +47,11 @@ public:
explicit YulUtilFunctions(
langutil::EVMVersion _evmVersion,
RevertStrings _revertStrings,
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector
MultiUseYulFunctionCollector& _functionCollector
):
m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings),
m_functionCollector(std::move(_functionCollector))
m_functionCollector(_functionCollector)
{}
/// @returns a function that combines the address and selector to a single value
@ -306,7 +306,7 @@ private:
langutil::EVMVersion m_evmVersion;
RevertStrings m_revertStrings;
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
MultiUseYulFunctionCollector& m_functionCollector;
};
}

View File

@ -100,7 +100,7 @@ string IRGenerationContext::newYulVariable()
string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
{
string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
return m_functions->createFunction(funName, [&]() {
return m_functions.createFunction(funName, [&]() {
Whiskers templ(R"(
function <functionName>(fun <comma> <in>) <arrow> <out> {
switch fun

View File

@ -56,11 +56,10 @@ public:
):
m_evmVersion(_evmVersion),
m_revertStrings(_revertStrings),
m_optimiserSettings(std::move(_optimiserSettings)),
m_functions(std::make_shared<MultiUseYulFunctionCollector>())
m_optimiserSettings(std::move(_optimiserSettings))
{}
std::shared_ptr<MultiUseYulFunctionCollector> functionCollector() const { return m_functions; }
MultiUseYulFunctionCollector& functionCollector() { return m_functions; }
/// Sets the current inheritance hierarchy from derived to base.
void setInheritanceHierarchy(std::vector<ContractDefinition const*> _hierarchy)
@ -108,7 +107,7 @@ private:
std::map<VariableDeclaration const*, IRVariable> m_localVariables;
/// Storage offsets of state variables
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
std::shared_ptr<MultiUseYulFunctionCollector> m_functions;
MultiUseYulFunctionCollector m_functions;
size_t m_varCounter = 0;
};

View File

@ -107,7 +107,7 @@ string IRGenerator::generate(ContractDefinition const& _contract)
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
for (auto const* fun: contract->definedFunctions())
generateFunction(*fun);
t("functions", m_context.functionCollector()->requestedFunctions());
t("functions", m_context.functionCollector().requestedFunctions());
resetContext(_contract);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
@ -116,7 +116,7 @@ string IRGenerator::generate(ContractDefinition const& _contract)
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
for (auto const* fun: contract->definedFunctions())
generateFunction(*fun);
t("runtimeFunctions", m_context.functionCollector()->requestedFunctions());
t("runtimeFunctions", m_context.functionCollector().requestedFunctions());
return t.render();
}
@ -130,7 +130,7 @@ string IRGenerator::generate(Block const& _block)
string IRGenerator::generateFunction(FunctionDefinition const& _function)
{
string functionName = m_context.functionName(_function);
return m_context.functionCollector()->createFunction(functionName, [&]() {
return m_context.functionCollector().createFunction(functionName, [&]() {
Whiskers t(R"(
function <functionName>(<params>) <returns> {
<body>
@ -160,7 +160,7 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
solAssert(_varDecl.isStateVariable(), "");
if (auto const* mappingType = dynamic_cast<MappingType const*>(type))
return m_context.functionCollector()->createFunction(functionName, [&]() {
return m_context.functionCollector().createFunction(functionName, [&]() {
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
solAssert(slot_offset.second == 0, "");
FunctionType funType(_varDecl);
@ -209,7 +209,7 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
{
solUnimplementedAssert(type->isValueType(), "");
return m_context.functionCollector()->createFunction(functionName, [&]() {
return m_context.functionCollector().createFunction(functionName, [&]() {
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
return Whiskers(R"(
@ -383,11 +383,10 @@ string IRGenerator::memoryInit()
void IRGenerator::resetContext(ContractDefinition const& _contract)
{
solAssert(
m_context.functionCollector()->requestedFunctions().empty(),
m_context.functionCollector().requestedFunctions().empty(),
"Reset context while it still had functions."
);
m_context = IRGenerationContext(m_evmVersion, m_context.revertStrings(), m_optimiserSettings);
m_utils = YulUtilFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
for (auto const& var: ContractType(_contract).stateVariables())

View File

@ -47,6 +47,7 @@ using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
using namespace std::string_literals;
namespace
{
@ -800,11 +801,19 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
case Type::Category::Function:
if (member == "selector")
{
solUnimplementedAssert(false, "");
solUnimplementedAssert(
dynamic_cast<FunctionType const&>(*_memberAccess.expression().annotation().type).kind() ==
FunctionType::Kind::External, ""
);
define(IRVariable{_memberAccess}, IRVariable(_memberAccess.expression()).part("functionIdentifier"));
}
else if (member == "address")
{
solUnimplementedAssert(false, "");
solUnimplementedAssert(
dynamic_cast<FunctionType const&>(*_memberAccess.expression().annotation().type).kind() ==
FunctionType::Kind::External, ""
);
define(IRVariable{_memberAccess}, IRVariable(_memberAccess.expression()).part("address"));
}
else
solAssert(
@ -1204,7 +1213,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
argumentTypes.emplace_back(&type(*arg));
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
}
string argumentString = ", " + joinHumanReadable(argumentStrings);
string argumentString = argumentStrings.empty() ? ""s : (", " + joinHumanReadable(argumentStrings));
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");

View File

@ -27,6 +27,8 @@
#include <libsolidity/ast/TypeProvider.h>
#include <libsolutil/Algorithms.h>
using namespace std;
using namespace solidity;
using namespace solidity::langutil;
@ -75,16 +77,39 @@ void CHC::analyze(SourceUnit const& _source)
m_context.setAssertionAccumulation(false);
m_variableUsage.setFunctionInlining(false);
resetSourceAnalysis();
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto genesisSort = make_shared<smt::FunctionSort>(
vector<smt::SortPointer>(),
boolSort
);
m_genesisPredicate = createSymbolicBlock(genesisSort, "genesis");
auto genesis = (*m_genesisPredicate)({});
addRule(genesis, genesis.name);
addRule(genesis(), "genesis");
_source.accept(*this);
set<SourceUnit const*, IdCompare> sources;
sources.insert(&_source);
for (auto const& source: _source.referencedSourceUnits(true))
sources.insert(source);
for (auto const* source: sources)
defineInterfacesAndSummaries(*source);
for (auto const* source: sources)
source->accept(*this);
for (auto const& [scope, target]: m_verificationTargets)
{
auto assertions = transactionAssertions(scope);
for (auto const* assertion: assertions)
{
createErrorBlock();
connectBlocks(target.value, error(), target.constraints && (target.errorId == assertion->id()));
auto [result, model] = query(error(), assertion->location());
// This should be fine but it's a bug in the old compiler
(void)model;
if (result == smt::CheckResult::UNSATISFIABLE)
m_safeAssertions.insert(assertion);
}
}
}
vector<string> CHC::unhandledQueries() const
@ -97,26 +122,15 @@ vector<string> CHC::unhandledQueries() const
bool CHC::visit(ContractDefinition const& _contract)
{
if (!shouldVisit(_contract))
return false;
reset();
resetContractAnalysis();
initContract(_contract);
m_stateVariables = _contract.stateVariablesIncludingInherited();
for (auto const& var: m_stateVariables)
// SMT solvers do not support function types as arguments.
if (var->type()->category() == Type::Category::Function)
m_stateSorts.push_back(make_shared<smt::Sort>(smt::Kind::Int));
else
m_stateSorts.push_back(smt::smtSort(*var->type()));
m_stateVariables = stateVariablesIncludingInheritedAndPrivate(_contract);
m_stateSorts = stateSorts(_contract);
clearIndices(&_contract);
string suffix = _contract.name() + "_" + to_string(_contract.id());
m_interfacePredicate = createSymbolicBlock(interfaceSort(), "interface_" + suffix);
// TODO create static instances for Bool/Int sorts in SolverInterface.
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
@ -125,10 +139,12 @@ bool CHC::visit(ContractDefinition const& _contract)
boolSort
);
string suffix = _contract.name() + "_" + to_string(_contract.id());
m_errorPredicate = createSymbolicBlock(errorFunctionSort, "error_" + suffix);
m_constructorPredicate = createSymbolicBlock(constructorSort(), "implicit_constructor_" + to_string(_contract.id()));
m_constructorSummaryPredicate = createSymbolicBlock(constructorSort(), "summary_constructor_" + suffix);
m_implicitConstructorPredicate = createSymbolicBlock(interfaceSort(), "implicit_constructor_" + suffix);
auto stateExprs = currentStateVariables();
setCurrentBlock(*m_interfacePredicate, &stateExprs);
setCurrentBlock(*m_interfaces.at(m_currentContract), &stateExprs);
SMTEncoder::visit(_contract);
return false;
@ -136,33 +152,33 @@ bool CHC::visit(ContractDefinition const& _contract)
void CHC::endVisit(ContractDefinition const& _contract)
{
if (!shouldVisit(_contract))
return;
for (auto const& var: m_stateVariables)
{
solAssert(m_context.knownVariable(*var), "");
auto const& symbVar = m_context.variable(*var);
symbVar->resetIndex();
m_context.setZeroValue(*var);
symbVar->increaseIndex();
}
auto genesisPred = (*m_genesisPredicate)({});
auto implicitConstructor = (*m_constructorPredicate)(currentStateVariables());
connectBlocks(genesisPred, implicitConstructor);
auto implicitConstructor = (*m_implicitConstructorPredicate)(initialStateVariables());
connectBlocks(genesis(), implicitConstructor);
m_currentBlock = implicitConstructor;
m_context.addAssertion(m_error.currentValue() == 0);
if (auto constructor = _contract.constructor())
constructor->accept(*this);
else
inlineConstructorHierarchy(_contract);
connectBlocks(m_currentBlock, interface());
auto summary = predicate(*m_constructorSummaryPredicate, vector<smt::Expression>{m_error.currentValue()} + currentStateVariables());
connectBlocks(m_currentBlock, summary);
for (unsigned i = 0; i < m_verificationTargets.size(); ++i)
{
auto const& target = m_verificationTargets.at(i);
auto errorAppl = error(i + 1);
if (query(errorAppl, target->location()))
m_safeAssertions.insert(target);
}
clearIndices(m_currentContract, nullptr);
auto stateExprs = vector<smt::Expression>{m_error.currentValue()} + currentStateVariables();
setCurrentBlock(*m_constructorSummaryPredicate, &stateExprs);
addVerificationTarget(m_currentContract, m_currentBlock, smt::Expression(true), m_error.currentValue());
connectBlocks(m_currentBlock, interface(), m_error.currentValue() == 0);
SMTEncoder::endVisit(_contract);
}
@ -182,7 +198,7 @@ bool CHC::visit(FunctionDefinition const& _function)
return false;
}
solAssert(!m_currentFunction, "Inlining internal function calls not yet implemented");
solAssert(!m_currentFunction, "Function inlining should not happen in CHC.");
m_currentFunction = &_function;
initFunction(_function);
@ -193,7 +209,17 @@ bool CHC::visit(FunctionDefinition const& _function)
auto functionPred = predicate(*functionEntryBlock, currentFunctionVariables());
auto bodyPred = predicate(*bodyBlock);
if (_function.isConstructor())
connectBlocks(m_currentBlock, functionPred);
else
connectBlocks(genesis(), functionPred);
m_context.addAssertion(m_error.currentValue() == 0);
for (auto const* var: m_stateVariables)
m_context.addAssertion(m_context.variable(*var)->valueAtIndex(0) == currentValue(*var));
for (auto const& var: _function.parameters())
m_context.addAssertion(m_context.variable(*var)->valueAtIndex(0) == currentValue(*var));
connectBlocks(functionPred, bodyPred);
setCurrentBlock(*bodyBlock);
@ -225,18 +251,30 @@ void CHC::endVisit(FunctionDefinition const& _function)
// This is done in endVisit(ContractDefinition).
if (_function.isConstructor())
{
auto constructorExit = createSymbolicBlock(interfaceSort(), "constructor_exit_" + to_string(_function.id()));
connectBlocks(m_currentBlock, predicate(*constructorExit, currentStateVariables()));
string suffix = m_currentContract->name() + "_" + to_string(m_currentContract->id());
auto constructorExit = createSymbolicBlock(constructorSort(), "constructor_exit_" + suffix);
connectBlocks(m_currentBlock, predicate(*constructorExit, vector<smt::Expression>{m_error.currentValue()} + currentStateVariables()));
clearIndices(m_currentContract, m_currentFunction);
auto stateExprs = currentStateVariables();
auto stateExprs = vector<smt::Expression>{m_error.currentValue()} + currentStateVariables();
setCurrentBlock(*constructorExit, &stateExprs);
}
else
{
connectBlocks(m_currentBlock, interface());
clearIndices(m_currentContract, m_currentFunction);
auto stateExprs = currentStateVariables();
setCurrentBlock(*m_interfacePredicate, &stateExprs);
auto assertionError = m_error.currentValue();
auto sum = summary(_function);
connectBlocks(m_currentBlock, sum);
auto iface = interface();
auto stateExprs = initialStateVariables();
setCurrentBlock(*m_interfaces.at(m_currentContract), &stateExprs);
if (_function.isPublic())
{
addVerificationTarget(&_function, m_currentBlock, sum, assertionError);
connectBlocks(m_currentBlock, iface, sum && (assertionError == 0));
}
}
m_currentFunction = nullptr;
}
@ -468,12 +506,23 @@ void CHC::visitAssert(FunctionCall const& _funCall)
solAssert(args.size() == 1, "");
solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
createErrorBlock();
solAssert(m_currentContract, "");
solAssert(m_currentFunction, "");
if (m_currentFunction->isConstructor())
m_functionAssertions[m_currentContract].insert(&_funCall);
else
m_functionAssertions[m_currentFunction].insert(&_funCall);
smt::Expression assertNeg = !(m_context.expression(*args.front())->currentValue());
connectBlocks(m_currentBlock, error(), currentPathConditions() && assertNeg);
auto previousError = m_error.currentValue();
m_error.increaseIndex();
m_verificationTargets.push_back(&_funCall);
connectBlocks(
m_currentBlock,
m_currentFunction->isConstructor() ? summary(*m_currentContract) : summary(*m_currentFunction),
currentPathConditions() && !m_context.expression(*args.front())->currentValue() && (m_error.currentValue() == _funCall.id())
);
m_context.addAssertion(m_error.currentValue() == previousError);
}
void CHC::unknownFunctionCall(FunctionCall const&)
@ -488,15 +537,23 @@ void CHC::unknownFunctionCall(FunctionCall const&)
m_unknownFunctionCallSeen = true;
}
void CHC::reset()
void CHC::resetSourceAnalysis()
{
m_verificationTargets.clear();
m_safeAssertions.clear();
m_functionAssertions.clear();
m_callGraph.clear();
m_summaries.clear();
}
void CHC::resetContractAnalysis()
{
m_stateSorts.clear();
m_stateVariables.clear();
m_verificationTargets.clear();
m_safeAssertions.clear();
m_unknownFunctionCallSeen = false;
m_breakDest = nullptr;
m_continueDest = nullptr;
m_error.resetIndex();
}
void CHC::eraseKnowledge()
@ -521,17 +578,6 @@ void CHC::clearIndices(ContractDefinition const* _contract, FunctionDefinition c
}
}
bool CHC::shouldVisit(ContractDefinition const& _contract) const
{
if (
_contract.isLibrary() ||
_contract.isInterface()
)
return false;
return true;
}
bool CHC::shouldVisit(FunctionDefinition const& _function) const
{
if (
@ -547,6 +593,7 @@ void CHC::setCurrentBlock(
vector<smt::Expression> const* _arguments
)
{
if (m_context.solverStackHeigh() > 0)
m_context.popSolver();
solAssert(m_currentContract, "");
clearIndices(m_currentContract, m_currentFunction);
@ -557,10 +604,42 @@ void CHC::setCurrentBlock(
m_currentBlock = predicate(_block);
}
set<Expression const*, CHC::IdCompare> CHC::transactionAssertions(ASTNode const* _txRoot)
{
set<Expression const*, IdCompare> assertions;
solidity::util::BreadthFirstSearch<ASTNode const*>{{_txRoot}}.run([&](auto const* function, auto&& _addChild) {
assertions.insert(m_functionAssertions[function].begin(), m_functionAssertions[function].end());
for (auto const* called: m_callGraph[function])
_addChild(called);
});
return assertions;
}
vector<VariableDeclaration const*> CHC::stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract)
{
vector<VariableDeclaration const*> stateVars;
for (auto const& contract: _contract.annotation().linearizedBaseContracts)
for (auto var: contract->stateVariables())
stateVars.push_back(var);
return stateVars;
}
vector<smt::SortPointer> CHC::stateSorts(ContractDefinition const& _contract)
{
vector<smt::SortPointer> stateSorts;
for (auto const& var: stateVariablesIncludingInheritedAndPrivate(_contract))
stateSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
return stateSorts;
}
smt::SortPointer CHC::constructorSort()
{
// TODO this will change once we support function calls.
return interfaceSort();
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
return make_shared<smt::FunctionSort>(
vector<smt::SortPointer>{intSort} + m_stateSorts,
boolSort
);
}
smt::SortPointer CHC::interfaceSort()
@ -572,20 +651,38 @@ smt::SortPointer CHC::interfaceSort()
);
}
smt::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
{
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
return make_shared<smt::FunctionSort>(
stateSorts(_contract),
boolSort
);
}
/// A function in the symbolic CFG requires:
/// - Index of failed assertion. 0 means no assertion failed.
/// - 2 sets of state variables:
/// - State variables at the beginning of the current function, immutable
/// - Current state variables
/// At the beginning of the function these must equal set 1
/// - 2 sets of input variables:
/// - Input variables at the beginning of the current function, immutable
/// - Current input variables
/// At the beginning of the function these must equal set 1
/// - 1 set of output variables
smt::SortPointer CHC::sort(FunctionDefinition const& _function)
{
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
vector<smt::SortPointer> varSorts;
for (auto const& var: _function.parameters() + _function.returnParameters())
{
// SMT solvers do not support function types as arguments.
if (var->type()->category() == Type::Category::Function)
varSorts.push_back(make_shared<smt::Sort>(smt::Kind::Int));
else
varSorts.push_back(smt::smtSort(*var->type()));
}
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
vector<smt::SortPointer> inputSorts;
for (auto const& var: _function.parameters())
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
vector<smt::SortPointer> outputSorts;
for (auto const& var: _function.returnParameters())
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
return make_shared<smt::FunctionSort>(
m_stateSorts + varSorts,
vector<smt::SortPointer>{intSort} + m_stateSorts + inputSorts + m_stateSorts + inputSorts + outputSorts,
boolSort
);
}
@ -601,19 +698,31 @@ smt::SortPointer CHC::sort(ASTNode const* _node)
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
vector<smt::SortPointer> varSorts;
for (auto const& var: m_currentFunction->localVariables())
{
// SMT solvers do not support function types as arguments.
if (var->type()->category() == Type::Category::Function)
varSorts.push_back(make_shared<smt::Sort>(smt::Kind::Int));
else
varSorts.push_back(smt::smtSort(*var->type()));
}
varSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
return make_shared<smt::FunctionSort>(
fSort->domain + varSorts,
boolSort
);
}
smt::SortPointer CHC::summarySort(FunctionDefinition const& _function, ContractDefinition const& _contract)
{
auto stateVariables = stateVariablesIncludingInheritedAndPrivate(_contract);
auto sorts = stateSorts(_contract);
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
vector<smt::SortPointer> inputSorts, outputSorts;
for (auto const& var: _function.parameters())
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
for (auto const& var: _function.returnParameters())
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
return make_shared<smt::FunctionSort>(
vector<smt::SortPointer>{intSort} + sorts + inputSorts + sorts + outputSorts,
boolSort
);
}
unique_ptr<smt::SymbolicFunctionVariable> CHC::createSymbolicBlock(smt::SortPointer _sort, string const& _name)
{
auto block = make_unique<smt::SymbolicFunctionVariable>(
@ -625,12 +734,33 @@ unique_ptr<smt::SymbolicFunctionVariable> CHC::createSymbolicBlock(smt::SortPoin
return block;
}
void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
{
for (auto const& node: _source.nodes())
if (auto const* contract = dynamic_cast<ContractDefinition const*>(node.get()))
for (auto const* base: contract->annotation().linearizedBaseContracts)
{
string suffix = base->name() + "_" + to_string(base->id());
m_interfaces[base] = createSymbolicBlock(interfaceSort(*base), "interface_" + suffix);
for (auto const* var: stateVariablesIncludingInheritedAndPrivate(*base))
if (!m_context.knownVariable(*var))
createVariable(*var);
for (auto const* function: base->definedFunctions())
m_summaries[contract].emplace(function, createSummaryBlock(*function, *contract));
}
}
smt::Expression CHC::interface()
{
vector<smt::Expression> paramExprs;
for (auto const& var: m_stateVariables)
paramExprs.push_back(m_context.variable(*var)->currentValue());
return (*m_interfacePredicate)(paramExprs);
return (*m_interfaces.at(m_currentContract))(paramExprs);
}
smt::Expression CHC::interface(ContractDefinition const& _contract)
{
return (*m_interfaces.at(&_contract))(stateVariablesAtIndex(0, _contract));
}
smt::Expression CHC::error()
@ -643,6 +773,27 @@ smt::Expression CHC::error(unsigned _idx)
return m_errorPredicate->functionValueAtIndex(_idx)({});
}
smt::Expression CHC::summary(ContractDefinition const&)
{
return (*m_constructorSummaryPredicate)(
vector<smt::Expression>{m_error.currentValue()} +
currentStateVariables()
);
}
smt::Expression CHC::summary(FunctionDefinition const& _function)
{
vector<smt::Expression> args{m_error.currentValue()};
auto contract = _function.annotation().contract;
args += contract->isLibrary() ? stateVariablesAtIndex(0, *contract) : initialStateVariables();
for (auto const& var: _function.parameters())
args.push_back(m_context.variable(*var)->valueAtIndex(0));
args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
for (auto const& var: _function.returnParameters())
args.push_back(m_context.variable(*var)->currentValue());
return (*m_summaries.at(m_currentContract).at(&_function))(args);
}
unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(ASTNode const* _node, string const& _prefix)
{
return createSymbolicBlock(sort(_node),
@ -653,6 +804,15 @@ unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(ASTNode const* _node,
predicateName(_node));
}
unique_ptr<smt::SymbolicFunctionVariable> CHC::createSummaryBlock(FunctionDefinition const& _function, ContractDefinition const& _contract)
{
return createSymbolicBlock(summarySort(_function, _contract),
"summary_" +
uniquePrefix() +
"_" +
predicateName(&_function, &_contract));
}
void CHC::createErrorBlock()
{
solAssert(m_errorPredicate, "");
@ -669,6 +829,28 @@ void CHC::connectBlocks(smt::Expression const& _from, smt::Expression const& _to
addRule(edge, _from.name + "_to_" + _to.name);
}
vector<smt::Expression> CHC::initialStateVariables()
{
return stateVariablesAtIndex(0);
}
vector<smt::Expression> CHC::stateVariablesAtIndex(int _index)
{
solAssert(m_currentContract, "");
vector<smt::Expression> exprs;
for (auto const& var: m_stateVariables)
exprs.push_back(m_context.variable(*var)->valueAtIndex(_index));
return exprs;
}
vector<smt::Expression> CHC::stateVariablesAtIndex(int _index, ContractDefinition const& _contract)
{
vector<smt::Expression> exprs;
for (auto const& var: stateVariablesIncludingInheritedAndPrivate(_contract))
exprs.push_back(m_context.variable(*var)->valueAtIndex(_index));
return exprs;
}
vector<smt::Expression> CHC::currentStateVariables()
{
solAssert(m_currentContract, "");
@ -680,11 +862,22 @@ vector<smt::Expression> CHC::currentStateVariables()
vector<smt::Expression> CHC::currentFunctionVariables()
{
vector<smt::Expression> paramExprs;
if (m_currentFunction)
for (auto const& var: m_currentFunction->parameters() + m_currentFunction->returnParameters())
paramExprs.push_back(m_context.variable(*var)->currentValue());
return currentStateVariables() + paramExprs;
vector<smt::Expression> initInputExprs;
vector<smt::Expression> mutableInputExprs;
for (auto const& var: m_currentFunction->parameters())
{
initInputExprs.push_back(m_context.variable(*var)->valueAtIndex(0));
mutableInputExprs.push_back(m_context.variable(*var)->currentValue());
}
vector<smt::Expression> returnExprs;
for (auto const& var: m_currentFunction->returnParameters())
returnExprs.push_back(m_context.variable(*var)->currentValue());
return vector<smt::Expression>{m_error.currentValue()} +
initialStateVariables() +
initInputExprs +
currentStateVariables() +
mutableInputExprs +
returnExprs;
}
vector<smt::Expression> CHC::currentBlockVariables()
@ -696,7 +889,7 @@ vector<smt::Expression> CHC::currentBlockVariables()
return currentFunctionVariables() + paramExprs;
}
string CHC::predicateName(ASTNode const* _node)
string CHC::predicateName(ASTNode const* _node, ContractDefinition const* _contract)
{
string prefix;
if (auto funDef = dynamic_cast<FunctionDefinition const*>(_node))
@ -705,7 +898,12 @@ string CHC::predicateName(ASTNode const* _node)
if (!funDef->name().empty())
prefix += "_" + funDef->name() + "_";
}
return prefix + to_string(_node->id());
else if (m_currentFunction && !m_currentFunction->name().empty())
prefix += m_currentFunction->name();
auto contract = _contract ? _contract : m_currentContract;
solAssert(contract, "");
return prefix + "_" + to_string(_node->id()) + "_" + to_string(contract->id());
}
smt::Expression CHC::predicate(smt::SymbolicFunctionVariable const& _block)
@ -726,7 +924,7 @@ void CHC::addRule(smt::Expression const& _rule, string const& _ruleName)
m_interface->addRule(_rule, _ruleName);
}
bool CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _location)
pair<smt::CheckResult, vector<string>> CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _location)
{
smt::CheckResult result;
vector<string> values;
@ -736,7 +934,7 @@ bool CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _
case smt::CheckResult::SATISFIABLE:
break;
case smt::CheckResult::UNSATISFIABLE:
return true;
break;
case smt::CheckResult::UNKNOWN:
break;
case smt::CheckResult::CONFLICTING:
@ -746,7 +944,12 @@ bool CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _
m_outerErrorReporter.warning(_location, "Error trying to invoke SMT solver.");
break;
}
return false;
return {result, values};
}
void CHC::addVerificationTarget(ASTNode const* _scope, smt::Expression _from, smt::Expression _constraints, smt::Expression _errorId)
{
m_verificationTargets.emplace(_scope, CHCVerificationTarget{{VerificationTarget::Type::Assert, _from, _constraints}, _errorId});
}
string CHC::uniquePrefix()

View File

@ -79,22 +79,38 @@ private:
void unknownFunctionCall(FunctionCall const& _funCall);
//@}
struct IdCompare
{
bool operator()(ASTNode const* lhs, ASTNode const* rhs) const
{
return lhs->id() < rhs->id();
}
};
/// Helpers.
//@{
void reset();
void resetSourceAnalysis();
void resetContractAnalysis();
void eraseKnowledge();
void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr) override;
bool shouldVisit(ContractDefinition const& _contract) const;
bool shouldVisit(FunctionDefinition const& _function) const;
void setCurrentBlock(smt::SymbolicFunctionVariable const& _block, std::vector<smt::Expression> const* _arguments = nullptr);
std::set<Expression const*, IdCompare> transactionAssertions(ASTNode const* _txRoot);
static std::vector<VariableDeclaration const*> stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract);
//@}
/// Sort helpers.
//@{
static std::vector<smt::SortPointer> stateSorts(ContractDefinition const& _contract);
smt::SortPointer constructorSort();
smt::SortPointer interfaceSort();
static smt::SortPointer interfaceSort(ContractDefinition const& _const);
smt::SortPointer sort(FunctionDefinition const& _function);
smt::SortPointer sort(ASTNode const* _block);
/// @returns the sort of a predicate that represents the summary of _function in the scope of _contract.
/// The _contract is also needed because the same function might be in many contracts due to inheritance,
/// where the sort changes because the set of state variables might change.
static smt::SortPointer summarySort(FunctionDefinition const& _function, ContractDefinition const& _contract);
//@}
/// Predicate helpers.
@ -102,14 +118,24 @@ private:
/// @returns a new block of given _sort and _name.
std::unique_ptr<smt::SymbolicFunctionVariable> createSymbolicBlock(smt::SortPointer _sort, std::string const& _name);
/// Creates summary predicates for all functions of all contracts
/// in a given _source.
void defineInterfacesAndSummaries(SourceUnit const& _source);
/// Genesis predicate.
smt::Expression genesis() { return (*m_genesisPredicate)({}); }
/// Interface predicate over current variables.
smt::Expression interface();
smt::Expression interface(ContractDefinition const& _contract);
/// Error predicate over current variables.
smt::Expression error();
smt::Expression error(unsigned _idx);
/// Creates a block for the given _node.
std::unique_ptr<smt::SymbolicFunctionVariable> createBlock(ASTNode const* _node, std::string const& _prefix = "");
/// Creates a call block for the given function _function from contract _contract.
/// The contract is needed here because of inheritance.
std::unique_ptr<smt::SymbolicFunctionVariable> createSummaryBlock(FunctionDefinition const& _function, ContractDefinition const& _contract);
/// Creates a new error block to be used by an assertion.
/// Also registers the predicate.
@ -117,6 +143,11 @@ private:
void connectBlocks(smt::Expression const& _from, smt::Expression const& _to, smt::Expression const& _constraints = smt::Expression(true));
/// @returns the symbolic values of the state variables at the beginning
/// of the current transaction.
std::vector<smt::Expression> initialStateVariables();
std::vector<smt::Expression> stateVariablesAtIndex(int _index);
std::vector<smt::Expression> stateVariablesAtIndex(int _index, ContractDefinition const& _contract);
/// @returns the current symbolic values of the current state variables.
std::vector<smt::Expression> currentStateVariables();
@ -128,19 +159,26 @@ private:
std::vector<smt::Expression> currentBlockVariables();
/// @returns the predicate name for a given node.
std::string predicateName(ASTNode const* _node);
std::string predicateName(ASTNode const* _node, ContractDefinition const* _contract = nullptr);
/// @returns a predicate application over the current scoped variables.
smt::Expression predicate(smt::SymbolicFunctionVariable const& _block);
/// @returns a predicate application over @param _arguments.
smt::Expression predicate(smt::SymbolicFunctionVariable const& _block, std::vector<smt::Expression> const& _arguments);
/// @returns a predicate that defines a constructor summary.
smt::Expression summary(ContractDefinition const& _contract);
/// @returns a predicate that defines a function summary.
smt::Expression summary(FunctionDefinition const& _function);
//@}
/// Solver related.
//@{
/// Adds Horn rule to the solver.
void addRule(smt::Expression const& _rule, std::string const& _ruleName);
/// @returns true if query is unsatisfiable (safe).
bool query(smt::Expression const& _query, langutil::SourceLocation const& _location);
/// @returns <true, empty> if query is unsatisfiable (safe).
/// @returns <false, model> otherwise.
std::pair<smt::CheckResult, std::vector<std::string>> query(smt::Expression const& _query, langutil::SourceLocation const& _location);
void addVerificationTarget(ASTNode const* _scope, smt::Expression _from, smt::Expression _constraints, smt::Expression _errorId);
//@}
/// Misc.
@ -157,15 +195,29 @@ private:
/// Implicit constructor predicate.
/// Explicit constructors are handled as functions.
std::unique_ptr<smt::SymbolicFunctionVariable> m_constructorPredicate;
std::unique_ptr<smt::SymbolicFunctionVariable> m_implicitConstructorPredicate;
/// Constructor summary predicate, exists after the constructor
/// (implicit or explicit) and before the interface.
std::unique_ptr<smt::SymbolicFunctionVariable> m_constructorSummaryPredicate;
/// Artificial Interface predicate.
/// Single entry block for all functions.
std::unique_ptr<smt::SymbolicFunctionVariable> m_interfacePredicate;
std::map<ContractDefinition const*, std::unique_ptr<smt::SymbolicFunctionVariable>> m_interfaces;
/// Artificial Error predicate.
/// Single error block for all assertions.
std::unique_ptr<smt::SymbolicFunctionVariable> m_errorPredicate;
/// Function predicates.
std::map<ContractDefinition const*, std::map<FunctionDefinition const*, std::unique_ptr<smt::SymbolicFunctionVariable>>> m_summaries;
smt::SymbolicIntVariable m_error{
TypeProvider::uint256(),
TypeProvider::uint256(),
"error",
m_context
};
//@}
/// Variables.
@ -180,7 +232,12 @@ private:
/// Verification targets.
//@{
std::vector<Expression const*> m_verificationTargets;
struct CHCVerificationTarget: VerificationTarget
{
smt::Expression errorId;
};
std::map<ASTNode const*, CHCVerificationTarget, IdCompare> m_verificationTargets;
/// Assertions proven safe.
std::set<Expression const*> m_safeAssertions;
@ -190,6 +247,10 @@ private:
//@{
FunctionDefinition const* m_currentFunction = nullptr;
std::map<ASTNode const*, std::set<ASTNode const*, IdCompare>, IdCompare> m_callGraph;
std::map<ASTNode const*, std::set<Expression const*>, IdCompare> m_functionAssertions;
/// The current block.
smt::Expression m_currentBlock = smt::Expression(true);

View File

@ -140,6 +140,7 @@ public:
void pushSolver();
void popSolver();
void addAssertion(Expression const& _e);
unsigned solverStackHeigh() { return m_assertions.size(); } const
SolverInterface* solver()
{
solAssert(m_solver, "");

View File

@ -29,9 +29,9 @@ ModelChecker::ModelChecker(
ReadCallback::Callback const& _smtCallback,
smt::SMTSolverChoice _enabledSolvers
):
m_context(),
m_bmc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers),
m_chc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers),
m_context()
m_chc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers)
{
}

View File

@ -62,14 +62,14 @@ public:
static smt::SMTSolverChoice availableSolvers();
private:
/// Stores the context of the encoding.
smt::EncodingContext m_context;
/// Bounded Model Checker engine.
BMC m_bmc;
/// Constrained Horn Clauses engine.
CHC m_chc;
/// Stores the context of the encoding.
smt::EncodingContext m_context;
};
}

View File

@ -407,19 +407,26 @@ void SMTEncoder::endVisit(TupleExpression const& _tuple)
auto const& symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_tuple));
solAssert(symbTuple, "");
auto const& symbComponents = symbTuple->components();
auto const& tupleComponents = _tuple.components();
solAssert(symbComponents.size() == _tuple.components().size(), "");
auto const* tupleComponents = &_tuple.components();
while (tupleComponents->size() == 1)
{
auto innerTuple = dynamic_pointer_cast<TupleExpression>(tupleComponents->front());
solAssert(innerTuple, "");
tupleComponents = &innerTuple->components();
}
solAssert(symbComponents.size() == tupleComponents->size(), "");
for (unsigned i = 0; i < symbComponents.size(); ++i)
{
auto sComponent = symbComponents.at(i);
auto tComponent = tupleComponents.at(i);
auto tComponent = tupleComponents->at(i);
if (sComponent && tComponent)
{
if (auto varDecl = identifierToVariable(*tComponent))
m_context.addAssertion(sComponent->currentValue() == currentValue(*varDecl));
else
{
solAssert(m_context.knownExpression(*tComponent), "");
if (!m_context.knownExpression(*tComponent))
createExpr(*tComponent);
m_context.addAssertion(sComponent->currentValue() == expr(*tComponent));
}
}

View File

@ -58,11 +58,11 @@ private:
z3::sort z3Sort(smt::Sort const& _sort);
z3::sort_vector z3Sort(std::vector<smt::SortPointer> const& _sorts);
std::map<std::string, z3::expr> m_constants;
std::map<std::string, z3::func_decl> m_functions;
z3::context m_context;
z3::solver m_solver;
std::map<std::string, z3::expr> m_constants;
std::map<std::string, z3::func_decl> m_functions;
};
}

View File

@ -565,7 +565,7 @@ string const* CompilerStack::sourceMapping(string const& _contractName) const
if (!c.sourceMapping)
{
if (auto items = assemblyItems(_contractName))
c.sourceMapping = make_unique<string>(computeSourceMapping(*items));
c.sourceMapping = make_unique<string>(evmasm::AssemblyItem::computeSourceMapping(*items, sourceIndices()));
}
return c.sourceMapping.get();
}
@ -579,7 +579,9 @@ string const* CompilerStack::runtimeSourceMapping(string const& _contractName) c
if (!c.runtimeSourceMapping)
{
if (auto items = runtimeAssemblyItems(_contractName))
c.runtimeSourceMapping = make_unique<string>(computeSourceMapping(*items));
c.runtimeSourceMapping = make_unique<string>(
evmasm::AssemblyItem::computeSourceMapping(*items, sourceIndices())
);
}
return c.runtimeSourceMapping.get();
}
@ -1005,7 +1007,6 @@ void CompilerStack::resolveImports()
if (ImportDirective const* import = dynamic_cast<ImportDirective*>(node.get()))
{
string const& path = import->annotation().absolutePath;
solAssert(!path.empty(), "");
solAssert(m_sources.count(path), "");
import->annotation().sourceUnit = m_sources[path].ast.get();
toposort(&m_sources[path]);
@ -1390,95 +1391,6 @@ bytes CompilerStack::createCBORMetadata(string const& _metadata, bool _experimen
return encoder.serialise();
}
string CompilerStack::computeSourceMapping(evmasm::AssemblyItems const& _items) const
{
if (m_stackState != CompilationSuccessful)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
string ret;
map<string, unsigned> sourceIndicesMap = sourceIndices();
int prevStart = -1;
int prevLength = -1;
int prevSourceIndex = -1;
size_t prevModifierDepth = -1;
char prevJump = 0;
for (auto const& item: _items)
{
if (!ret.empty())
ret += ";";
SourceLocation const& location = item.location();
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()) :
-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;
unsigned components = 5;
if (modifierDepth == prevModifierDepth)
{
components--;
if (jump == prevJump)
{
components--;
if (sourceIndex == prevSourceIndex)
{
components--;
if (length == prevLength)
{
components--;
if (location.start == prevStart)
components--;
}
}
}
}
if (components-- > 0)
{
if (location.start != prevStart)
ret += to_string(location.start);
if (components-- > 0)
{
ret += ':';
if (length != prevLength)
ret += to_string(length);
if (components-- > 0)
{
ret += ':';
if (sourceIndex != prevSourceIndex)
ret += to_string(sourceIndex);
if (components-- > 0)
{
ret += ':';
if (jump != prevJump)
ret += jump;
if (components-- > 0)
{
ret += ':';
if (modifierDepth != prevModifierDepth)
ret += to_string(modifierDepth);
}
}
}
}
}
prevStart = location.start;
prevLength = length;
prevSourceIndex = sourceIndex;
prevJump = jump;
prevModifierDepth = modifierDepth;
}
return ret;
}
namespace
{

View File

@ -401,9 +401,6 @@ private:
/// @returns the metadata CBOR for the given serialised metadata JSON.
bytes createCBORMetadata(std::string const& _metadata, bool _experimentalMode);
/// @returns the computer source mapping string.
std::string computeSourceMapping(evmasm::AssemblyItems const& _items) const;
/// @returns the contract ABI as a JSON object.
/// This will generate the JSON object and store it in the Contract object if it is not present yet.
Json::Value const& contractABI(Contract const&) const;

View File

@ -1081,7 +1081,7 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
{ "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" },
wildcardMatchesExperimental
))
output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, nullptr);
output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, object.sourceMappings.get());
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesExperimental))
output["contracts"][sourceName][contractName]["irOptimized"] = stack.print();

View File

@ -695,7 +695,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
);
bool isIndexed = false;
bool isDeclaredConst = false;
VariableDeclaration::Constantness constantness = VariableDeclaration::Constantness::Mutable;
ASTPointer<OverrideSpecifier> overrides = nullptr;
Visibility visibility(Visibility::Default);
VariableDeclaration::Location location = VariableDeclaration::Location::Unspecified;
@ -731,7 +731,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
if (_options.allowIndexed && token == Token::Indexed)
isIndexed = true;
else if (token == Token::Constant)
isDeclaredConst = true;
constantness = VariableDeclaration::Constantness::Constant;
else if (_options.allowLocationSpecifier && TokenTraits::isLocationSpecifier(token))
{
if (location != VariableDeclaration::Location::Unspecified)
@ -790,7 +790,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
visibility,
_options.isStateVariable,
isIndexed,
isDeclaredConst,
constantness,
overrides,
location
);

View File

@ -82,7 +82,6 @@ AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect,
vector<YulString> AsmAnalyzer::operator()(Literal const& _literal)
{
expectValidType(_literal.type, _literal.location);
if (_literal.kind == LiteralKind::String && _literal.value.str().size() > 32)
typeError(
_literal.location,
@ -93,6 +92,13 @@ vector<YulString> AsmAnalyzer::operator()(Literal const& _literal)
else if (_literal.kind == LiteralKind::Boolean)
yulAssert(_literal.value == "true"_yulstring || _literal.value == "false"_yulstring, "");
if (!m_dialect.validTypeForLiteral(_literal.kind, _literal.value, _literal.type))
typeError(
_literal.location,
"Invalid type \"" + _literal.type.str() + "\" for literal \"" + _literal.value.str() + "\"."
);
return {_literal.type};
}
@ -179,7 +185,8 @@ void AsmAnalyzer::operator()(Assignment const& _assignment)
);
for (size_t i = 0; i < numVariables; ++i)
checkAssignment(_assignment.variableNames[i]);
if (i < types.size())
checkAssignment(_assignment.variableNames[i], types[i]);
}
void AsmAnalyzer::operator()(VariableDeclaration const& _varDecl)
@ -193,6 +200,9 @@ void AsmAnalyzer::operator()(VariableDeclaration const& _varDecl)
yul::IdentifierContext::VariableDeclaration,
m_currentScope->insideFunction()
);
for (auto const& variable: _varDecl.variables)
expectValidType(variable.type, variable.location);
if (_varDecl.value)
{
vector<YulString> types = std::visit(*this, *_varDecl.value);
@ -204,16 +214,26 @@ void AsmAnalyzer::operator()(VariableDeclaration const& _varDecl)
to_string(types.size()) +
" values."
);
for (size_t i = 0; i < _varDecl.variables.size(); ++i)
{
YulString givenType = m_dialect.defaultType;
if (i < types.size())
givenType = types[i];
TypedName const& variable = _varDecl.variables[i];
if (variable.type != givenType)
typeError(
variable.location,
"Assigning value of type \"" + givenType.str() + "\" to variable of type \"" + variable.type.str() + "."
);
}
}
for (TypedName const& variable: _varDecl.variables)
{
expectValidType(variable.type, variable.location);
m_activeVariables.insert(&std::get<Scope::Variable>(
m_currentScope->identifiers.at(variable.name))
);
}
}
void AsmAnalyzer::operator()(FunctionDefinition const& _funDef)
{
@ -291,6 +311,11 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
);
}
}
std::reverse(argTypes.begin(), argTypes.end());
if (parameterTypes && parameterTypes->size() == argTypes.size())
for (size_t i = 0; i < parameterTypes->size(); ++i)
expectType((*parameterTypes)[i], argTypes[i], locationOf(_funCall.arguments[i]));
if (m_success)
{
@ -306,7 +331,7 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
void AsmAnalyzer::operator()(If const& _if)
{
expectExpression(*_if.condition);
expectBoolExpression(*_if.condition);
(*this)(_if.body);
}
@ -315,32 +340,15 @@ void AsmAnalyzer::operator()(Switch const& _switch)
{
yulAssert(_switch.expression, "");
expectExpression(*_switch.expression);
YulString caseType;
bool mismatchingTypes = false;
for (auto const& _case: _switch.cases)
if (_case.value)
{
if (caseType.empty())
caseType = _case.value->type;
else if (caseType != _case.value->type)
{
mismatchingTypes = true;
break;
}
}
if (mismatchingTypes)
m_errorReporter.typeError(
_switch.location,
"Switch cases have non-matching types."
);
YulString valueType = expectExpression(*_switch.expression);
set<u256> cases;
for (auto const& _case: _switch.cases)
{
if (_case.value)
{
expectType(valueType, _case.value->type, _case.value->location);
// We cannot use "expectExpression" here because *_case.value is not an
// Expression and would be converted to an Expression otherwise.
(*this)(*_case.value);
@ -366,8 +374,7 @@ void AsmAnalyzer::operator()(ForLoop const& _for)
// condition, the body and the post part inside.
m_currentScope = &scope(&_for.pre);
expectExpression(*_for.condition);
expectBoolExpression(*_for.condition);
// backup outer for-loop & create new state
auto outerForLoop = m_currentForLoop;
m_currentForLoop = &_for;
@ -403,10 +410,24 @@ YulString AsmAnalyzer::expectExpression(Expression const& _expr)
return types.empty() ? m_dialect.defaultType : types.front();
}
void AsmAnalyzer::checkAssignment(Identifier const& _variable)
void AsmAnalyzer::expectBoolExpression(Expression const& _expr)
{
YulString type = expectExpression(_expr);
if (type != m_dialect.boolType)
typeError(locationOf(_expr),
"Expected a value of boolean type \"" +
m_dialect.boolType.str() +
"\" but got \"" +
type.str() +
"\""
);
}
void AsmAnalyzer::checkAssignment(Identifier const& _variable, YulString _valueType)
{
yulAssert(!_variable.name.empty(), "");
size_t numErrorsBefore = m_errorReporter.errors().size();
YulString const* variableType = nullptr;
bool found = false;
if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name))
{
@ -418,6 +439,8 @@ void AsmAnalyzer::checkAssignment(Identifier const& _variable)
_variable.location,
"Variable " + _variable.name.str() + " used before it was declared."
);
else
variableType = &std::get<Scope::Variable>(*var).type;
found = true;
}
else if (m_resolver)
@ -427,6 +450,7 @@ void AsmAnalyzer::checkAssignment(Identifier const& _variable)
if (variableSize != size_t(-1))
{
found = true;
variableType = &m_dialect.defaultType;
yulAssert(variableSize == 1, "Invalid stack size of external reference.");
}
}
@ -438,6 +462,17 @@ void AsmAnalyzer::checkAssignment(Identifier const& _variable)
if (numErrorsBefore == m_errorReporter.errors().size())
declarationError(_variable.location, "Variable not found or variable not lvalue.");
}
if (variableType && *variableType != _valueType)
typeError(_variable.location,
"Assigning a value of type \"" +
_valueType.str() +
"\" to a variable of type \"" +
variableType->str() +
"\"."
);
if (m_success)
yulAssert(variableType, "");
}
Scope& AsmAnalyzer::scope(Block const* _block)
@ -447,15 +482,28 @@ Scope& AsmAnalyzer::scope(Block const* _block)
yulAssert(scopePtr, "Scope requested but not present.");
return *scopePtr;
}
void AsmAnalyzer::expectValidType(YulString _type, SourceLocation const& _location)
{
if (!_type.empty() && !m_dialect.types.count(_type))
m_errorReporter.typeError(
if (!m_dialect.types.count(_type))
typeError(
_location,
"\"" + _type.str() + "\" is not a valid type (user defined types are not yet supported)."
);
}
void AsmAnalyzer::expectType(YulString _expectedType, YulString _givenType, SourceLocation const& _location)
{
if (_expectedType != _givenType)
typeError(_location,
"Expected a value of type \"" +
_expectedType.str() +
"\" but got \"" +
_givenType.str() +
"\""
);
}
bool AsmAnalyzer::warnOnInstructions(std::string const& _instructionIdentifier, langutil::SourceLocation const& _location)
{
auto const builtin = EVMDialect::strictAssemblyForEVM(EVMVersion{}).builtin(YulString(_instructionIdentifier));

View File

@ -96,13 +96,18 @@ private:
/// Visits the expression, expects that it evaluates to exactly one value and
/// returns the type. Reports errors on errors and returns the default type.
YulString expectExpression(Expression const& _expr);
/// Vists the expression and expects it to return a single boolean value.
/// Reports an error otherwise.
void expectBoolExpression(Expression const& _expr);
bool expectDeposit(int _deposit, int _oldHeight, langutil::SourceLocation const& _location);
/// Verifies that a variable to be assigned to exists and can be assigned to.
void checkAssignment(Identifier const& _variable);
/// Verifies that a variable to be assigned to exists, can be assigned to
/// and has the same type as the value.
void checkAssignment(Identifier const& _variable, YulString _valueType);
Scope& scope(Block const* _block);
void expectValidType(YulString _type, langutil::SourceLocation const& _location);
void expectType(YulString _expectedType, YulString _givenType, langutil::SourceLocation const& _location);
bool warnOnInstructions(evmasm::Instruction _instr, langutil::SourceLocation const& _location);
bool warnOnInstructions(std::string const& _instrIdentifier, langutil::SourceLocation const& _location);

View File

@ -204,6 +204,12 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const
compileEVM(adapter, false, m_optimiserSettings.optimizeStackAllocation);
object.bytecode = make_shared<evmasm::LinkerObject>(assembly.assemble());
object.assembly = assembly.assemblyString();
object.sourceMappings = make_unique<string>(
evmasm::AssemblyItem::computeSourceMapping(
assembly.items(),
{{scanner().charStream() ? scanner().charStream()->name() : "", 0}}
)
);
return object;
}
case Machine::EVM15:

View File

@ -48,6 +48,7 @@ struct MachineAssemblyObject
{
std::shared_ptr<evmasm::LinkerObject> bytecode;
std::string assembly;
std::unique_ptr<std::string> sourceMappings;
};
/*
@ -114,6 +115,8 @@ private:
std::shared_ptr<yul::Object> m_parserResult;
langutil::ErrorList m_errors;
langutil::ErrorReporter m_errorReporter;
std::unique_ptr<std::string> m_sourceMappings;
};
}

View File

@ -154,6 +154,8 @@ add_library(yul
optimiser/Suite.h
optimiser/SyntacticalEquality.cpp
optimiser/SyntacticalEquality.h
optimiser/TypeInfo.cpp
optimiser/TypeInfo.h
optimiser/UnusedPruner.cpp
optimiser/UnusedPruner.h
optimiser/VarDeclInitializer.cpp

View File

@ -0,0 +1,38 @@
/*
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/>.
*/
#pragma once
#include <set>
namespace solidity::yul
{
/**
* Side effects of code related to control flow.
*/
struct ControlFlowSideEffects
{
/// If true, this code terminates the control flow.
/// State may or may not be reverted as indicated by the ``reverts`` flag.
bool terminates = false;
/// If true, this code reverts all state changes in the transaction.
/// Whenever this is true, ``terminates`` has to be true as well.
bool reverts = false;
};
}

View File

@ -19,9 +19,26 @@
*/
#include <libyul/Dialect.h>
#include <libyul/AsmData.h>
using namespace solidity::yul;
using namespace std;
using namespace solidity::langutil;
Literal Dialect::zeroLiteralForType(solidity::yul::YulString _type) const
{
if (_type == boolType && _type != defaultType)
return {SourceLocation{}, LiteralKind::Boolean, "false"_yulstring, _type};
return {SourceLocation{}, LiteralKind::Number, "0"_yulstring, _type};
}
bool Dialect::validTypeForLiteral(LiteralKind _kind, YulString, YulString _type) const
{
if (_kind == LiteralKind::Boolean)
return _type == boolType;
else
return true;
}
Dialect const& Dialect::yulDeprecated()
{

View File

@ -22,6 +22,7 @@
#include <libyul/YulString.h>
#include <libyul/SideEffects.h>
#include <libyul/ControlFlowSideEffects.h>
#include <boost/noncopyable.hpp>
@ -33,6 +34,8 @@ namespace solidity::yul
class YulString;
using Type = YulString;
enum class LiteralKind;
struct Literal;
struct BuiltinFunction
{
@ -40,6 +43,7 @@ struct BuiltinFunction
std::vector<Type> parameters;
std::vector<Type> returns;
SideEffects sideEffects;
ControlFlowSideEffects controlFlowSideEffects;
/// If true, this is the msize instruction.
bool isMSize = false;
/// If true, can only accept literals as arguments and they cannot be moved to variables.
@ -52,15 +56,21 @@ struct Dialect: boost::noncopyable
YulString defaultType;
/// Type used for the literals "true" and "false".
YulString boolType;
std::set<YulString> types;
std::set<YulString> types = {{}};
/// @returns the builtin function of the given name or a nullptr if it is not a builtin function.
virtual BuiltinFunction const* builtin(YulString /*_name*/) const { return nullptr; }
virtual BuiltinFunction const* discardFunction() const { return nullptr; }
virtual BuiltinFunction const* equalityFunction() const { return nullptr; }
virtual BuiltinFunction const* discardFunction(YulString /* _type */) const { return nullptr; }
virtual BuiltinFunction const* equalityFunction(YulString /* _type */) const { return nullptr; }
virtual BuiltinFunction const* booleanNegationFunction() const { return nullptr; }
/// Check whether the given type is legal for the given literal value.
/// Should only be called if the type exists in the dialect at all.
virtual bool validTypeForLiteral(LiteralKind _kind, YulString _value, YulString _type) const;
virtual Literal zeroLiteralForType(YulString _type) const;
virtual std::set<YulString> fixedFunctionNames() const { return {}; }
Dialect() = default;

View File

@ -52,6 +52,8 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
f.parameters.resize(info.args);
f.returns.resize(info.ret);
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
f.controlFlowSideEffects.terminates = evmasm::SemanticInformation::terminatesControlFlow(_instruction);
f.controlFlowSideEffects.reverts = evmasm::SemanticInformation::reverts(_instruction);
f.isMSize = _instruction == evmasm::Instruction::MSIZE;
f.literalArguments = false;
f.instruction = _instruction;
@ -290,6 +292,28 @@ EVMDialectTyped::EVMDialectTyped(langutil::EVMVersion _evmVersion, bool _objectA
m_functions["u256_to_bool"_yulstring].returns = {"bool"_yulstring};
}
BuiltinFunctionForEVM const* EVMDialectTyped::discardFunction(YulString _type) const
{
if (_type == "bool"_yulstring)
return builtin("popbool"_yulstring);
else
{
yulAssert(_type == defaultType, "");
return builtin("pop"_yulstring);
}
}
BuiltinFunctionForEVM const* EVMDialectTyped::equalityFunction(YulString _type) const
{
if (_type == "bool"_yulstring)
return nullptr;
else
{
yulAssert(_type == defaultType, "");
return builtin("eq"_yulstring);
}
}
EVMDialectTyped const& EVMDialectTyped::instance(langutil::EVMVersion _version)
{
static map<langutil::EVMVersion, unique_ptr<EVMDialectTyped const>> dialects;

View File

@ -68,8 +68,8 @@ struct EVMDialect: public Dialect
/// @returns the builtin function of the given name or a nullptr if it is not a builtin function.
BuiltinFunctionForEVM const* builtin(YulString _name) const override;
BuiltinFunctionForEVM const* discardFunction() const override { return builtin("pop"_yulstring); }
BuiltinFunctionForEVM const* equalityFunction() const override { return builtin("eq"_yulstring); }
BuiltinFunctionForEVM const* discardFunction(YulString /*_type*/) const override { return builtin("pop"_yulstring); }
BuiltinFunctionForEVM const* equalityFunction(YulString /*_type*/) const override { return builtin("eq"_yulstring); }
BuiltinFunctionForEVM const* booleanNegationFunction() const override { return builtin("iszero"_yulstring); }
static EVMDialect const& strictAssemblyForEVM(langutil::EVMVersion _version);
@ -102,6 +102,10 @@ struct EVMDialectTyped: public EVMDialect
/// Constructor, should only be used internally. Use the factory function below.
EVMDialectTyped(langutil::EVMVersion _evmVersion, bool _objectAccess);
BuiltinFunctionForEVM const* discardFunction(YulString _type) const override;
BuiltinFunctionForEVM const* equalityFunction(YulString _type) const override;
BuiltinFunctionForEVM const* booleanNegationFunction() const override { return builtin("not"_yulstring); }
static EVMDialectTyped const& instance(langutil::EVMVersion _version);
};

View File

@ -1235,7 +1235,7 @@ Object EVMToEwasmTranslator::run(Object const& _object)
MainFunction{}(ast);
ForLoopConditionIntoBody::run(context, ast);
ExpressionSplitter::run(context, ast);
WordSizeTransform::run(m_dialect, WasmDialect::instance().defaultType, ast, nameDispenser);
WordSizeTransform::run(m_dialect, WasmDialect::instance(), ast, nameDispenser);
NameDisplacer{nameDispenser, m_polyfillFunctions}(ast);
for (auto const& st: m_polyfill->statements)
@ -1251,8 +1251,12 @@ Object EVMToEwasmTranslator::run(Object const& _object)
AsmAnalyzer analyzer(*ret.analysisInfo, errorReporter, WasmDialect::instance(), {}, _object.dataNames());
if (!analyzer.analyze(*ret.code))
{
// TODO the errors here are "wrong" because they have invalid source references!
string message;
string message = "Invalid code generated after EVM to wasm translation.\n";
message += "Note that the source locations in the errors below will reference the original, not the translated code.\n";
message += "Translated code:\n";
message += "----------------------------------\n";
message += ret.toString(&WasmDialect::instance());
message += "----------------------------------\n";
for (auto const& err: errors)
message += langutil::SourceReferenceFormatter::formatErrorInformation(*err);
yulAssert(false, message);

View File

@ -91,13 +91,16 @@ WasmDialect::WasmDialect()
m_functions["i64.load"_yulstring].sideEffects.sideEffectFreeIfNoMSize = true;
// Drop is actually overloaded for all types, but Yul does not support that.
// We could introduce "i32.drop".
// Because of that, we introduce "i32.drop".
addFunction("drop", {i64}, {});
addFunction("i32.drop", {i32}, {});
addFunction("nop", {}, {});
addFunction("unreachable", {}, {}, false);
m_functions["unreachable"_yulstring].sideEffects.invalidatesStorage = false;
m_functions["unreachable"_yulstring].sideEffects.invalidatesMemory = false;
m_functions["unreachable"_yulstring].controlFlowSideEffects.terminates = true;
m_functions["unreachable"_yulstring].controlFlowSideEffects.reverts = true;
addFunction("datasize", {i64}, {i64}, true, true);
addFunction("dataoffset", {i64}, {i64}, true, true);
@ -114,6 +117,22 @@ BuiltinFunction const* WasmDialect::builtin(YulString _name) const
return nullptr;
}
BuiltinFunction const* WasmDialect::discardFunction(YulString _type) const
{
if (_type == "i32"_yulstring)
return builtin("i32.drop"_yulstring);
yulAssert(_type == "i64"_yulstring, "");
return builtin("drop"_yulstring);
}
BuiltinFunction const* WasmDialect::equalityFunction(YulString _type) const
{
if (_type == "i32"_yulstring)
return builtin("i32.eq"_yulstring);
yulAssert(_type == "i64"_yulstring, "");
return builtin("i64.eq"_yulstring);
}
WasmDialect const& WasmDialect::instance()
{
static std::unique_ptr<WasmDialect> dialect;
@ -130,7 +149,13 @@ void WasmDialect::addEthereumExternals()
static string const i64{"i64"};
static string const i32{"i32"};
static string const i32ptr{"i32"}; // Uses "i32" on purpose.
struct External { string name; vector<string> parameters; vector<string> returns; };
struct External
{
string name;
vector<string> parameters;
vector<string> returns;
ControlFlowSideEffects controlFlowSideEffects = ControlFlowSideEffects{};
};
static vector<External> externals{
{"getAddress", {i32ptr}, {}},
{"getExternalBalance", {i32ptr, i32ptr}, {}},
@ -158,11 +183,11 @@ void WasmDialect::addEthereumExternals()
{"log", {i32ptr, i32, i32, i32ptr, i32ptr, i32ptr, i32ptr}, {}},
{"getBlockNumber", {}, {i64}},
{"getTxOrigin", {i32ptr}, {}},
{"finish", {i32ptr, i32}, {}},
{"revert", {i32ptr, i32}, {}},
{"finish", {i32ptr, i32}, {}, ControlFlowSideEffects{true, false}},
{"revert", {i32ptr, i32}, {}, ControlFlowSideEffects{true, true}},
{"getReturnDataSize", {}, {i32}},
{"returnDataCopy", {i32ptr, i32, i32}, {}},
{"selfDestruct", {i32ptr}, {}},
{"selfDestruct", {i32ptr}, {}, ControlFlowSideEffects{true, false}},
{"getBlockTimestamp", {}, {i64}}
};
for (External const& ext: externals)
@ -176,6 +201,7 @@ void WasmDialect::addEthereumExternals()
f.returns.emplace_back(YulString(p));
// TODO some of them are side effect free.
f.sideEffects = SideEffects::worst();
f.controlFlowSideEffects = ext.controlFlowSideEffects;
f.isMSize = false;
f.sideEffects.invalidatesStorage = (ext.name == "storageStore");
f.literalArguments = false;

View File

@ -35,19 +35,19 @@ struct Object;
/**
* Yul dialect for Wasm as a backend.
*
* Builtin functions are a subset of the wasm instructions, always implicitly assuming
* unsigned 64 bit types.
* Builtin functions are a subset of the wasm instructions.
*
* There is a builtin function `i32.drop` that takes an i32, while `drop` takes i64.
*
* !This is subject to changes!
*/
struct WasmDialect: public Dialect
{
WasmDialect();
BuiltinFunction const* builtin(YulString _name) const override;
BuiltinFunction const* discardFunction() const override { return builtin("drop"_yulstring); }
BuiltinFunction const* equalityFunction() const override { return builtin("i64.eq"_yulstring); }
BuiltinFunction const* booleanNegationFunction() const override { return builtin("i64.eqz"_yulstring); }
BuiltinFunction const* discardFunction(YulString _type) const override;
BuiltinFunction const* equalityFunction(YulString _type) const override;
BuiltinFunction const* booleanNegationFunction() const override { return builtin("i32.eqz"_yulstring); }
std::set<YulString> fixedFunctionNames() const override { return {"main"_yulstring}; }

View File

@ -45,7 +45,7 @@ void WordSizeTransform::operator()(FunctionCall& _fc)
if (fun->literalArguments)
{
for (Expression& arg: _fc.arguments)
get<Literal>(arg).type = m_defaultType;
get<Literal>(arg).type = m_targetDialect.defaultType;
return;
}
@ -106,12 +106,17 @@ void WordSizeTransform::operator()(Block& _block)
for (int i = 0; i < 3; i++)
ret.push_back(VariableDeclaration{
varDecl.location,
{TypedName{varDecl.location, newLhs[i], m_defaultType}},
make_unique<Expression>(Literal{locationOf(*varDecl.value), LiteralKind::Number, "0"_yulstring, m_defaultType})
{TypedName{varDecl.location, newLhs[i], m_targetDialect.defaultType}},
make_unique<Expression>(Literal{
locationOf(*varDecl.value),
LiteralKind::Number,
"0"_yulstring,
m_targetDialect.defaultType
})
});
ret.push_back(VariableDeclaration{
varDecl.location,
{TypedName{varDecl.location, newLhs[3], m_defaultType}},
{TypedName{varDecl.location, newLhs[3], m_targetDialect.defaultType}},
std::move(varDecl.value)
});
return {std::move(ret)};
@ -133,7 +138,7 @@ void WordSizeTransform::operator()(Block& _block)
ret.push_back(
VariableDeclaration{
varDecl.location,
{TypedName{varDecl.location, newLhs[i], m_defaultType}},
{TypedName{varDecl.location, newLhs[i], m_targetDialect.defaultType}},
std::move(newRhs[i])
}
);
@ -163,7 +168,12 @@ void WordSizeTransform::operator()(Block& _block)
ret.push_back(Assignment{
assignment.location,
{Identifier{assignment.location, newLhs[i]}},
make_unique<Expression>(Literal{locationOf(*assignment.value), LiteralKind::Number, "0"_yulstring, m_defaultType})
make_unique<Expression>(Literal{
locationOf(*assignment.value),
LiteralKind::Number,
"0"_yulstring,
m_targetDialect.defaultType
})
});
ret.push_back(Assignment{
assignment.location,
@ -209,14 +219,25 @@ void WordSizeTransform::operator()(Block& _block)
void WordSizeTransform::run(
Dialect const& _inputDialect,
YulString _targetDefaultType,
Dialect const& _targetDialect,
Block& _ast,
NameDispenser& _nameDispenser
)
{
// Free the name `or_bool`.
NameDisplacer{_nameDispenser, {"or_bool"_yulstring}}(_ast);
WordSizeTransform{_inputDialect, _nameDispenser, _targetDefaultType}(_ast);
WordSizeTransform{_inputDialect, _targetDialect, _nameDispenser}(_ast);
}
WordSizeTransform::WordSizeTransform(
Dialect const& _inputDialect,
Dialect const& _targetDialect,
NameDispenser& _nameDispenser
):
m_inputDialect(_inputDialect),
m_targetDialect(_targetDialect),
m_nameDispenser(_nameDispenser)
{
}
void WordSizeTransform::rewriteVarDeclList(TypedNameList& _nameList)
@ -227,7 +248,7 @@ void WordSizeTransform::rewriteVarDeclList(TypedNameList& _nameList)
{
TypedNameList ret;
for (auto newName: generateU64IdentifierNames(_n.name))
ret.emplace_back(TypedName{_n.location, newName, m_defaultType});
ret.emplace_back(TypedName{_n.location, newName, m_targetDialect.defaultType});
return ret;
}
);
@ -291,7 +312,7 @@ vector<Statement> WordSizeTransform::handleSwitchInternal(
for (auto& c: cases)
{
Literal label{_location, LiteralKind::Number, YulString(c.first.str()), m_defaultType};
Literal label{_location, LiteralKind::Number, YulString(c.first.str()), m_targetDialect.defaultType};
ret.cases.emplace_back(Case{
c.second.front().location,
make_unique<Literal>(std::move(label)),
@ -312,7 +333,7 @@ vector<Statement> WordSizeTransform::handleSwitchInternal(
Assignment{
_location,
{{_location, _runDefaultFlag}},
make_unique<Expression>(Literal{_location, LiteralKind::Number, "1"_yulstring, m_defaultType})
make_unique<Expression>(Literal{_location, LiteralKind::Boolean, "true"_yulstring, m_targetDialect.boolType})
}
)}
});
@ -337,7 +358,7 @@ std::vector<Statement> WordSizeTransform::handleSwitch(Switch& _switch)
_switch.cases.pop_back();
ret.emplace_back(VariableDeclaration{
_switch.location,
{TypedName{_switch.location, runDefaultFlag, m_defaultType}},
{TypedName{_switch.location, runDefaultFlag, m_targetDialect.boolType}},
{}
});
}
@ -392,7 +413,7 @@ array<unique_ptr<Expression>, 4> WordSizeTransform::expandValue(Expression const
lit.location,
LiteralKind::Number,
YulString(currentVal.str()),
m_defaultType
m_targetDialect.defaultType
}
);
}

View File

@ -53,7 +53,7 @@ namespace solidity::yul
* take four times the parameters and each of type u64.
* In addition, it uses a single other builtin function called `or_bool` that
* takes four u64 parameters and is supposed to return the logical disjunction
* of them as a u64 value. If this name is already used somewhere, it is renamed.
* of them as a i32 value. If this name is already used somewhere, it is renamed.
*
* Prerequisite: Disambiguator, ForLoopConditionIntoBody, ExpressionSplitter
*/
@ -69,7 +69,7 @@ public:
static void run(
Dialect const& _inputDialect,
YulString _targetDefaultType,
Dialect const& _targetDialect,
Block& _ast,
NameDispenser& _nameDispenser
);
@ -77,13 +77,9 @@ public:
private:
explicit WordSizeTransform(
Dialect const& _inputDialect,
NameDispenser& _nameDispenser,
YulString _defaultType
):
m_inputDialect(_inputDialect),
m_nameDispenser(_nameDispenser),
m_defaultType(_defaultType)
{ }
Dialect const& _targetDialect,
NameDispenser& _nameDispenser
);
void rewriteVarDeclList(std::vector<TypedName>&);
void rewriteIdentifierList(std::vector<Identifier>&);
@ -103,8 +99,8 @@ private:
std::vector<Expression> expandValueToVector(Expression const& _e);
Dialect const& m_inputDialect;
Dialect const& m_targetDialect;
NameDispenser& m_nameDispenser;
YulString m_defaultType;
/// maps original u256 variable's name to corresponding u64 variables' names
std::map<YulString, std::array<YulString, 4>> m_variableMapping;
};

View File

@ -77,12 +77,7 @@ void ConditionalSimplifier::operator()(Block& _block)
Assignment{
location,
{Identifier{location, condition}},
make_unique<Expression>(Literal{
location,
LiteralKind::Number,
"0"_yulstring,
{}
})
make_unique<Expression>(m_dialect.zeroLiteralForType(m_dialect.boolType))
}
);
}

View File

@ -17,6 +17,7 @@
#include <libyul/optimiser/ControlFlowSimplifier.h>
#include <libyul/optimiser/Semantics.h>
#include <libyul/optimiser/OptimiserStep.h>
#include <libyul/optimiser/TypeInfo.h>
#include <libyul/AsmData.h>
#include <libyul/Utilities.h>
#include <libyul/Dialect.h>
@ -38,14 +39,13 @@ namespace
ExpressionStatement makeDiscardCall(
langutil::SourceLocation const& _location,
Dialect const& _dialect,
BuiltinFunction const& _discardFunction,
Expression&& _expression
)
{
yulAssert(_dialect.discardFunction(), "No discard function available.");
return {_location, FunctionCall{
_location,
Identifier{_location, _dialect.discardFunction()->name},
Identifier{_location, _discardFunction.name},
{std::move(_expression)}
}};
}
@ -74,62 +74,12 @@ void removeEmptyCasesFromSwitch(Switch& _switchStmt)
);
}
OptionalStatements reduceNoCaseSwitch(Dialect const& _dialect, Switch& _switchStmt)
{
yulAssert(_switchStmt.cases.empty(), "Expected no case!");
if (!_dialect.discardFunction())
return {};
auto loc = locationOf(*_switchStmt.expression);
return make_vector<Statement>(makeDiscardCall(
loc,
_dialect,
std::move(*_switchStmt.expression)
));
}
OptionalStatements reduceSingleCaseSwitch(Dialect const& _dialect, Switch& _switchStmt)
{
yulAssert(_switchStmt.cases.size() == 1, "Expected only one case!");
auto& switchCase = _switchStmt.cases.front();
auto loc = locationOf(*_switchStmt.expression);
if (switchCase.value)
{
if (!_dialect.equalityFunction())
return {};
return make_vector<Statement>(If{
std::move(_switchStmt.location),
make_unique<Expression>(FunctionCall{
loc,
Identifier{loc, _dialect.equalityFunction()->name},
{std::move(*switchCase.value), std::move(*_switchStmt.expression)}
}),
std::move(switchCase.body)
});
}
else
{
if (!_dialect.discardFunction())
return {};
return make_vector<Statement>(
makeDiscardCall(
loc,
_dialect,
std::move(*_switchStmt.expression)
),
std::move(switchCase.body)
);
}
}
}
void ControlFlowSimplifier::run(OptimiserStepContext& _context, Block& _ast)
{
ControlFlowSimplifier{_context.dialect}(_ast);
TypeInfo typeInfo(_context.dialect, _ast);
ControlFlowSimplifier{_context.dialect, typeInfo}(_ast);
}
void ControlFlowSimplifier::operator()(Block& _block)
@ -194,12 +144,12 @@ void ControlFlowSimplifier::simplify(std::vector<yul::Statement>& _statements)
GenericVisitor visitor{
VisitorFallback<OptionalStatements>{},
[&](If& _ifStmt) -> OptionalStatements {
if (_ifStmt.body.statements.empty() && m_dialect.discardFunction())
if (_ifStmt.body.statements.empty() && m_dialect.discardFunction(m_dialect.boolType))
{
OptionalStatements s = vector<Statement>{};
s->emplace_back(makeDiscardCall(
_ifStmt.location,
m_dialect,
*m_dialect.discardFunction(m_dialect.boolType),
std::move(*_ifStmt.condition)
));
return s;
@ -211,9 +161,9 @@ void ControlFlowSimplifier::simplify(std::vector<yul::Statement>& _statements)
removeEmptyCasesFromSwitch(_switchStmt);
if (_switchStmt.cases.empty())
return reduceNoCaseSwitch(m_dialect, _switchStmt);
return reduceNoCaseSwitch(_switchStmt);
else if (_switchStmt.cases.size() == 1)
return reduceSingleCaseSwitch(m_dialect, _switchStmt);
return reduceSingleCaseSwitch(_switchStmt);
return {};
}
@ -231,3 +181,58 @@ void ControlFlowSimplifier::simplify(std::vector<yul::Statement>& _statements)
}
);
}
OptionalStatements ControlFlowSimplifier::reduceNoCaseSwitch(Switch& _switchStmt) const
{
yulAssert(_switchStmt.cases.empty(), "Expected no case!");
BuiltinFunction const* discardFunction =
m_dialect.discardFunction(m_typeInfo.typeOf(*_switchStmt.expression));
if (!discardFunction)
return {};
auto loc = locationOf(*_switchStmt.expression);
return make_vector<Statement>(makeDiscardCall(
loc,
*discardFunction,
std::move(*_switchStmt.expression)
));
}
OptionalStatements ControlFlowSimplifier::reduceSingleCaseSwitch(Switch& _switchStmt) const
{
yulAssert(_switchStmt.cases.size() == 1, "Expected only one case!");
auto& switchCase = _switchStmt.cases.front();
auto loc = locationOf(*_switchStmt.expression);
YulString type = m_typeInfo.typeOf(*_switchStmt.expression);
if (switchCase.value)
{
if (!m_dialect.equalityFunction(type))
return {};
return make_vector<Statement>(If{
std::move(_switchStmt.location),
make_unique<Expression>(FunctionCall{
loc,
Identifier{loc, m_dialect.equalityFunction(type)->name},
{std::move(*switchCase.value), std::move(*_switchStmt.expression)}
}),
std::move(switchCase.body)
});
}
else
{
if (!m_dialect.discardFunction(type))
return {};
return make_vector<Statement>(
makeDiscardCall(
loc,
*m_dialect.discardFunction(type),
std::move(*_switchStmt.expression)
),
std::move(switchCase.body)
);
}
}

View File

@ -23,6 +23,7 @@ namespace solidity::yul
{
struct Dialect;
struct OptimiserStepContext;
class TypeInfo;
/**
* Simplifies several control-flow structures:
@ -61,11 +62,18 @@ public:
void visit(Statement& _st) override;
private:
ControlFlowSimplifier(Dialect const& _dialect): m_dialect(_dialect) {}
ControlFlowSimplifier(Dialect const& _dialect, TypeInfo const& _typeInfo):
m_dialect(_dialect),
m_typeInfo(_typeInfo)
{}
void simplify(std::vector<Statement>& _statements);
std::optional<std::vector<Statement>> reduceNoCaseSwitch(Switch& _switchStmt) const;
std::optional<std::vector<Statement>> reduceSingleCaseSwitch(Switch& _switchStmt) const;
Dialect const& m_dialect;
TypeInfo const& m_typeInfo;
size_t m_numBreakStatements = 0;
size_t m_numContinueStatements = 0;
};

View File

@ -23,11 +23,13 @@
#include <libyul/optimiser/ASTWalker.h>
#include <libyul/optimiser/OptimiserStep.h>
#include <libyul/optimiser/TypeInfo.h>
#include <libyul/AsmData.h>
#include <libyul/Dialect.h>
#include <libsolutil/CommonData.h>
#include <libsolutil/Visitor.h>
#include <boost/range/adaptor/reversed.hpp>
@ -39,7 +41,8 @@ using namespace solidity::langutil;
void ExpressionSplitter::run(OptimiserStepContext& _context, Block& _ast)
{
ExpressionSplitter{_context.dialect, _context.dispenser}(_ast);
TypeInfo typeInfo(_context.dialect, _ast);
ExpressionSplitter{_context.dialect, _context.dispenser, typeInfo}(_ast);
}
void ExpressionSplitter::operator()(FunctionCall& _funCall)
@ -103,10 +106,13 @@ void ExpressionSplitter::outlineExpression(Expression& _expr)
SourceLocation location = locationOf(_expr);
YulString var = m_nameDispenser.newName({});
YulString type = m_typeInfo.typeOf(_expr);
m_statementsToPrefix.emplace_back(VariableDeclaration{
location,
{{TypedName{location, var, {}}}},
{{TypedName{location, var, type}}},
make_unique<Expression>(std::move(_expr))
});
_expr = Identifier{location, var};
m_typeInfo.setVariableType(var, type);
}

View File

@ -30,9 +30,9 @@
namespace solidity::yul
{
class NameCollector;
struct Dialect;
struct OptimiserStepContext;
class TypeInfo;
/**
* Optimiser component that modifies an AST in place, turning complex
@ -68,8 +68,14 @@ public:
void operator()(Block& _block) override;
private:
explicit ExpressionSplitter(Dialect const& _dialect, NameDispenser& _nameDispenser):
m_dialect(_dialect), m_nameDispenser(_nameDispenser)
explicit ExpressionSplitter(
Dialect const& _dialect,
NameDispenser& _nameDispenser,
TypeInfo& _typeInfo
):
m_dialect(_dialect),
m_nameDispenser(_nameDispenser),
m_typeInfo(_typeInfo)
{ }
/// Replaces the expression by a variable if it is a function call or functional
@ -82,6 +88,7 @@ private:
std::vector<Statement> m_statementsToPrefix;
Dialect const& m_dialect;
NameDispenser& m_nameDispenser;
TypeInfo& m_typeInfo;
};
}

View File

@ -56,9 +56,9 @@ void ForLoopConditionIntoBody::operator()(ForLoop& _forLoop)
_forLoop.condition = make_unique<Expression>(
Literal {
loc,
LiteralKind::Number,
"1"_yulstring,
{}
LiteralKind::Boolean,
"true"_yulstring,
m_dialect.boolType
}
);
}

View File

@ -29,6 +29,7 @@
#include <libyul/optimiser/Semantics.h>
#include <libyul/Exceptions.h>
#include <libyul/AsmData.h>
#include <libyul/Dialect.h>
#include <libsolutil/CommonData.h>
#include <libsolutil/Visitor.h>
@ -41,11 +42,11 @@ using namespace solidity::yul;
void FullInliner::run(OptimiserStepContext& _context, Block& _ast)
{
FullInliner{_ast, _context.dispenser}.run();
FullInliner{_ast, _context.dispenser, _context.dialect}.run();
}
FullInliner::FullInliner(Block& _ast, NameDispenser& _dispenser):
m_ast(_ast), m_nameDispenser(_dispenser)
FullInliner::FullInliner(Block& _ast, NameDispenser& _dispenser, Dialect const& _dialect):
m_ast(_ast), m_nameDispenser(_dispenser), m_dialect(_dialect)
{
// Determine constants
SSAValueTracker tracker;
@ -139,7 +140,7 @@ void FullInliner::updateCodeSize(FunctionDefinition const& _fun)
void FullInliner::handleBlock(YulString _currentFunctionName, Block& _block)
{
InlineModifier{*this, m_nameDispenser, _currentFunctionName}(_block);
InlineModifier{*this, m_nameDispenser, _currentFunctionName, m_dialect}(_block);
}
bool FullInliner::recursive(FunctionDefinition const& _fun) const
@ -198,7 +199,7 @@ vector<Statement> InlineModifier::performInline(Statement& _statement, FunctionC
if (_value)
varDecl.value = make_unique<Expression>(std::move(*_value));
else
varDecl.value = make_unique<Expression>(Literal{{}, LiteralKind::Number, YulString{"0"}, {}});
varDecl.value = make_unique<Expression>(m_dialect.zeroLiteralForType(varDecl.variables.front().type));
newStatements.emplace_back(std::move(varDecl));
};

View File

@ -69,7 +69,7 @@ class FullInliner: public ASTModifier
{
public:
static constexpr char const* name{"FullInliner"};
static void run(OptimiserStepContext&, Block& _ast);
static void run(OptimiserStepContext& _context, Block& _ast);
/// Inlining heuristic.
/// @param _callSite the name of the function in which the function call is located.
@ -89,7 +89,7 @@ public:
void tentativelyUpdateCodeSize(YulString _function, YulString _callSite);
private:
FullInliner(Block& _ast, NameDispenser& _dispenser);
FullInliner(Block& _ast, NameDispenser& _dispenser, Dialect const& _dialect);
void run();
void updateCodeSize(FunctionDefinition const& _fun);
@ -108,6 +108,7 @@ private:
std::set<YulString> m_constants;
std::map<YulString, size_t> m_functionSizes;
NameDispenser& m_nameDispenser;
Dialect const& m_dialect;
};
/**
@ -117,10 +118,11 @@ private:
class InlineModifier: public ASTModifier
{
public:
InlineModifier(FullInliner& _driver, NameDispenser& _nameDispenser, YulString _functionName):
InlineModifier(FullInliner& _driver, NameDispenser& _nameDispenser, YulString _functionName, Dialect const& _dialect):
m_currentFunction(std::move(_functionName)),
m_driver(_driver),
m_nameDispenser(_nameDispenser)
m_nameDispenser(_nameDispenser),
m_dialect(_dialect)
{ }
void operator()(Block& _block) override;
@ -132,6 +134,7 @@ private:
YulString m_currentFunction;
FullInliner& m_driver;
NameDispenser& m_nameDispenser;
Dialect const& m_dialect;
};
/**

View File

@ -27,6 +27,8 @@
#include <libsolutil/CommonData.h>
#include <libyul/optimiser/TypeInfo.h>
using namespace std;
using namespace solidity;
using namespace solidity::yul;
@ -42,8 +44,14 @@ namespace
class IntroduceSSA: public ASTModifier
{
public:
explicit IntroduceSSA(NameDispenser& _nameDispenser, set<YulString> const& _variablesToReplace):
m_nameDispenser(_nameDispenser), m_variablesToReplace(_variablesToReplace)
explicit IntroduceSSA(
NameDispenser& _nameDispenser,
set<YulString> const& _variablesToReplace,
TypeInfo& _typeInfo
):
m_nameDispenser(_nameDispenser),
m_variablesToReplace(_variablesToReplace),
m_typeInfo(_typeInfo)
{ }
void operator()(Block& _block) override;
@ -51,6 +59,7 @@ public:
private:
NameDispenser& m_nameDispenser;
set<YulString> const& m_variablesToReplace;
TypeInfo const& m_typeInfo;
};
@ -83,10 +92,10 @@ void IntroduceSSA::operator()(Block& _block)
{
YulString oldName = var.name;
YulString newName = m_nameDispenser.newName(oldName);
newVariables.emplace_back(TypedName{loc, newName, {}});
newVariables.emplace_back(TypedName{loc, newName, var.type});
statements.emplace_back(VariableDeclaration{
loc,
{TypedName{loc, oldName, {}}},
{TypedName{loc, oldName, var.type}},
make_unique<Expression>(Identifier{loc, newName})
});
}
@ -110,7 +119,11 @@ void IntroduceSSA::operator()(Block& _block)
{
YulString oldName = var.name;
YulString newName = m_nameDispenser.newName(oldName);
newVariables.emplace_back(TypedName{loc, newName, {}});
newVariables.emplace_back(TypedName{
loc,
newName,
m_typeInfo.typeOfVariable(oldName)
});
statements.emplace_back(Assignment{
loc,
{Identifier{loc, oldName}},
@ -136,9 +149,12 @@ class IntroduceControlFlowSSA: public ASTModifier
public:
explicit IntroduceControlFlowSSA(
NameDispenser& _nameDispenser,
set<YulString> const& _variablesToReplace
set<YulString> const& _variablesToReplace,
TypeInfo const& _typeInfo
):
m_nameDispenser(_nameDispenser), m_variablesToReplace(_variablesToReplace)
m_nameDispenser(_nameDispenser),
m_variablesToReplace(_variablesToReplace),
m_typeInfo(_typeInfo)
{ }
void operator()(FunctionDefinition& _function) override;
@ -153,6 +169,7 @@ private:
set<YulString> m_variablesInScope;
/// Set of variables that do not have a specific value.
set<YulString> m_variablesToReassign;
TypeInfo const& m_typeInfo;
};
void IntroduceControlFlowSSA::operator()(FunctionDefinition& _function)
@ -221,7 +238,7 @@ void IntroduceControlFlowSSA::operator()(Block& _block)
YulString newName = m_nameDispenser.newName(toReassign);
toPrepend.emplace_back(VariableDeclaration{
locationOf(_s),
{TypedName{locationOf(_s), newName, {}}},
{TypedName{locationOf(_s), newName, m_typeInfo.typeOfVariable(toReassign)}},
make_unique<Expression>(Identifier{locationOf(_s), toReassign})
});
assignedVariables.insert(toReassign);
@ -375,10 +392,11 @@ void PropagateValues::operator()(Block& _block)
void SSATransform::run(OptimiserStepContext& _context, Block& _ast)
{
TypeInfo typeInfo(_context.dialect, _ast);
Assignments assignments;
assignments(_ast);
IntroduceSSA{_context.dispenser, assignments.names()}(_ast);
IntroduceControlFlowSSA{_context.dispenser, assignments.names()}(_ast);
IntroduceSSA{_context.dispenser, assignments.names(), typeInfo}(_ast);
IntroduceControlFlowSSA{_context.dispenser, assignments.names(), typeInfo}(_ast);
PropagateValues{assignments.names()}(_ast);
}

View File

@ -0,0 +1,103 @@
/*
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/>.
*/
/**
* Helper class that keeps track of the types while performing optimizations.
*/
#include <libyul/optimiser/TypeInfo.h>
#include <libyul/optimiser/NameCollector.h>
#include <libyul/AsmData.h>
#include <libyul/Dialect.h>
#include <libsolutil/Visitor.h>
using namespace std;
using namespace solidity::yul;
using namespace solidity::util;
class TypeInfo::TypeCollector: public ASTWalker
{
public:
explicit TypeCollector(Block const& _block)
{
(*this)(_block);
}
using ASTWalker::operator();
void operator()(VariableDeclaration const& _varDecl) override
{
for (auto const& var: _varDecl.variables)
variableTypes[var.name] = var.type;
}
void operator()(FunctionDefinition const& _funDef) override
{
ASTWalker::operator()(_funDef);
auto& funType = functionTypes[_funDef.name];
for (auto const arg: _funDef.parameters)
{
funType.parameters.emplace_back(arg.type);
variableTypes[arg.name] = arg.type;
}
for (auto const ret: _funDef.returnVariables)
{
funType.returns.emplace_back(ret.type);
variableTypes[ret.name] = ret.type;
}
}
std::map<YulString, YulString> variableTypes;
std::map<YulString, FunctionType> functionTypes;
};
TypeInfo::TypeInfo(Dialect const& _dialect, Block const& _ast):
m_dialect(_dialect)
{
TypeCollector types(_ast);
m_functionTypes = std::move(types.functionTypes);
m_variableTypes = std::move(types.variableTypes);
}
YulString TypeInfo::typeOf(Expression const& _expression) const
{
return std::visit(GenericVisitor{
[&](FunctionCall const& _funCall) {
YulString name = _funCall.functionName.name;
vector<YulString> const* retTypes = nullptr;
if (BuiltinFunction const* fun = m_dialect.builtin(name))
retTypes = &fun->returns;
else
retTypes = &m_functionTypes.at(name).returns;
yulAssert(retTypes && retTypes->size() == 1, "Call to typeOf for non-single-value expression.");
return retTypes->front();
},
[&](Identifier const& _identifier) {
return m_variableTypes.at(_identifier.name);
},
[&](Literal const& _literal) {
return _literal.type;
}
}, _expression);
}
YulString TypeInfo::typeOfVariable(YulString _name) const
{
return m_variableTypes.at(_name);
}

View File

@ -0,0 +1,64 @@
/*
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/>.
*/
/**
* Helper class that keeps track of the types while performing optimizations.
*/
#pragma once
#include <libyul/AsmDataForward.h>
#include <libyul/YulString.h>
#include <vector>
#include <map>
namespace solidity::yul
{
struct Dialect;
/**
* Helper class that keeps track of the types while performing optimizations.
*
* Only works on disambiguated sources!
*/
class TypeInfo
{
public:
TypeInfo(Dialect const& _dialect, Block const& _ast);
void setVariableType(YulString _name, YulString _type) { m_variableTypes[_name] = _type; }
/// @returns the type of an expression that is assumed to return exactly one value.
YulString typeOf(Expression const& _expression) const;
/// \returns the type of variable
YulString typeOfVariable(YulString _name) const;
private:
class TypeCollector;
struct FunctionType
{
std::vector<YulString> parameters;
std::vector<YulString> returns;
};
Dialect const& m_dialect;
std::map<YulString, YulString> m_variableTypes;
std::map<YulString, FunctionType> m_functionTypes;
};
}

View File

@ -100,10 +100,10 @@ void UnusedPruner::operator()(Block& _block)
subtractReferences(ReferencesCounter::countReferences(*varDecl.value));
statement = Block{std::move(varDecl.location), {}};
}
else if (varDecl.variables.size() == 1 && m_dialect.discardFunction())
else if (varDecl.variables.size() == 1 && m_dialect.discardFunction(varDecl.variables.front().type))
statement = ExpressionStatement{varDecl.location, FunctionCall{
varDecl.location,
{varDecl.location, m_dialect.discardFunction()->name},
{varDecl.location, m_dialect.discardFunction(varDecl.variables.front().type)->name},
{*std::move(varDecl.value)}
}};
}

View File

@ -20,6 +20,7 @@
#include <libsolutil/CommonData.h>
#include <libsolutil/Visitor.h>
#include <libyul/Dialect.h>
using namespace std;
using namespace solidity;
@ -32,14 +33,14 @@ void VarDeclInitializer::operator()(Block& _block)
using OptionalStatements = std::optional<vector<Statement>>;
util::GenericVisitor visitor{
util::VisitorFallback<OptionalStatements>{},
[](VariableDeclaration& _varDecl) -> OptionalStatements
[this](VariableDeclaration& _varDecl) -> OptionalStatements
{
if (_varDecl.value)
return {};
Literal zero{{}, LiteralKind::Number, YulString{"0"}, {}};
if (_varDecl.variables.size() == 1)
{
_varDecl.value = make_unique<Expression>(std::move(zero));
_varDecl.value = make_unique<Expression>(m_dialect.zeroLiteralForType(_varDecl.variables.front().type));
return {};
}
else
@ -47,7 +48,10 @@ void VarDeclInitializer::operator()(Block& _block)
OptionalStatements ret{vector<Statement>{}};
langutil::SourceLocation loc{std::move(_varDecl.location)};
for (auto& var: _varDecl.variables)
ret->emplace_back(VariableDeclaration{loc, {std::move(var)}, make_unique<Expression>(zero)});
{
unique_ptr<Expression> expr = make_unique<Expression >(m_dialect.zeroLiteralForType(var.type));
ret->emplace_back(VariableDeclaration{loc, {std::move(var)}, std::move(expr)});
}
return ret;
}
}

View File

@ -34,9 +34,14 @@ class VarDeclInitializer: public ASTModifier
{
public:
static constexpr char const* name{"VarDeclInitializer"};
static void run(OptimiserStepContext&, Block& _ast) { VarDeclInitializer{}(_ast); }
static void run(OptimiserStepContext& _ctx, Block& _ast) { VarDeclInitializer{_ctx.dialect}(_ast); }
void operator()(Block& _block) override;
private:
explicit VarDeclInitializer(Dialect const& _dialect): m_dialect(_dialect) {}
Dialect const& m_dialect;
};
}

View File

@ -10,6 +10,7 @@ SOLC=${REPO_ROOT}/${SOLIDITY_BUILD_DIR}/solc/solc
SPLITSOURCES=${REPO_ROOT}/scripts/splitSources.py
SYNTAXTESTS_DIR="${REPO_ROOT}/test/libsolidity/syntaxTests"
ASTJSONTESTS_DIR="${REPO_ROOT}/test/libsolidity/ASTJSON"
NSOURCES="$(find $SYNTAXTESTS_DIR -type f | wc -l)"
# DEV_DIR="${REPO_ROOT}/../tmp/contracts/"
@ -75,7 +76,7 @@ echo "Looking at $NSOURCES .sol files..."
WORKINGDIR=$PWD
# for solfile in $(find $DEV_DIR -name *.sol)
for solfile in $(find $SYNTAXTESTS_DIR -name *.sol)
for solfile in $(find $SYNTAXTESTS_DIR $ASTJSONTESTS_DIR -name *.sol)
do
echo -n "."
# create a temporary sub-directory

View File

@ -126,7 +126,7 @@ EOF
git config user.email "chris@ethereum.org"
git clean -f -d -x
DIRNAME=$(cd "$REPO_ROOT" && git show -s --format="%cd-%H" --date=short)
DIRNAME=$(cd "$REPO_ROOT" && git show -s --format="%cd-%H" --date="format:%Y-%m-%d-%H-%M")
mkdir -p "$DIRNAME"
REPORT="$DIRNAME/$ZIP_SUFFIX.txt"
cp ../report.txt "$REPORT"

View File

@ -127,6 +127,7 @@ static string const g_strInterface = "interface";
static string const g_strYul = "yul";
static string const g_strYulDialect = "yul-dialect";
static string const g_strIR = "ir";
static string const g_strIROptimized = "ir-optimized";
static string const g_strIPFS = "ipfs";
static string const g_strLicense = "license";
static string const g_strLibraries = "libraries";
@ -190,6 +191,7 @@ static string const g_argImportAst = g_strImportAst;
static string const g_argInputFile = g_strInputFile;
static string const g_argYul = g_strYul;
static string const g_argIR = g_strIR;
static string const g_argIROptimized = g_strIROptimized;
static string const g_argEwasm = g_strEwasm;
static string const g_argLibraries = g_strLibraries;
static string const g_argLink = g_strLink;
@ -336,8 +338,9 @@ void CommandLineInterface::handleOpcode(string const& _contract)
void CommandLineInterface::handleIR(string const& _contractName)
{
if (m_args.count(g_argIR))
{
if (!m_args.count(g_argIR))
return;
if (m_args.count(g_argOutputDir))
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".yul", m_compiler->yulIR(_contractName));
else
@ -346,12 +349,26 @@ void CommandLineInterface::handleIR(string const& _contractName)
sout() << m_compiler->yulIR(_contractName) << endl;
}
}
void CommandLineInterface::handleIROptimized(string const& _contractName)
{
if (!m_args.count(g_argIROptimized))
return;
if (m_args.count(g_argOutputDir))
createFile(m_compiler->filesystemFriendlyName(_contractName) + "_opt.yul", m_compiler->yulIROptimized(_contractName));
else
{
sout() << "Optimized IR:" << endl;
sout() << m_compiler->yulIROptimized(_contractName) << endl;
}
}
void CommandLineInterface::handleEwasm(string const& _contractName)
{
if (m_args.count(g_argEwasm))
{
if (!m_args.count(g_argEwasm))
return;
if (m_args.count(g_argOutputDir))
{
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".wast", m_compiler->ewasm(_contractName));
@ -367,7 +384,6 @@ void CommandLineInterface::handleEwasm(string const& _contractName)
sout() << "Ewasm binary (hex): " << m_compiler->ewasmObject(_contractName).toHex() << endl;
}
}
}
void CommandLineInterface::handleBytecode(string const& _contract)
{
@ -812,6 +828,7 @@ Allowed options)",
(g_argBinaryRuntime.c_str(), "Binary of the runtime part of the contracts in hex.")
(g_argAbi.c_str(), "ABI specification of the contracts.")
(g_argIR.c_str(), "Intermediate Representation (IR) of all contracts (EXPERIMENTAL).")
(g_argIROptimized.c_str(), "Optimized intermediate Representation (IR) of all contracts (EXPERIMENTAL).")
(g_argEwasm.c_str(), "Ewasm text representation of all contracts (EXPERIMENTAL).")
(g_argSignatureHashes.c_str(), "Function signature hashes of the contracts.")
(g_argNatspecUser.c_str(), "Natspec user documentation of all contracts.")
@ -1123,7 +1140,7 @@ bool CommandLineInterface::processInput()
m_compiler->setRevertStringBehaviour(m_revertStrings);
// TODO: Perhaps we should not compile unless requested
m_compiler->enableIRGeneration(m_args.count(g_argIR));
m_compiler->enableIRGeneration(m_args.count(g_argIR) || m_args.count(g_argIROptimized));
m_compiler->enableEwasmGeneration(m_args.count(g_argEwasm));
OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::standard() : OptimiserSettings::minimal();
@ -1631,6 +1648,7 @@ void CommandLineInterface::outputCompilationResults()
handleBytecode(contract);
handleIR(contract);
handleIROptimized(contract);
handleEwasm(contract);
handleSignatureHashes(contract);
handleMetadata(contract);

View File

@ -65,6 +65,7 @@ private:
void handleBinary(std::string const& _contract);
void handleOpcode(std::string const& _contract);
void handleIR(std::string const& _contract);
void handleIROptimized(std::string const& _contract);
void handleEwasm(std::string const& _contract);
void handleBytecode(std::string const& _contract);
void handleSignatureHashes(std::string const& _contract);

View File

@ -143,16 +143,22 @@ set(yul_phaser_sources
yulPhaser/Common.cpp
yulPhaser/CommonTest.cpp
yulPhaser/Chromosome.cpp
yulPhaser/FitnessMetrics.cpp
yulPhaser/GeneticAlgorithms.cpp
yulPhaser/Population.cpp
yulPhaser/Program.cpp
yulPhaser/Selections.cpp
yulPhaser/SimulationRNG.cpp
# FIXME: yul-phaser is not a library so I can't just add it to target_link_libraries().
# My current workaround is just to include its source files here but this introduces
# unnecessary duplication. Create a library or find a way to reuse the list in both places.
../tools/yulPhaser/Chromosome.cpp
../tools/yulPhaser/FitnessMetrics.cpp
../tools/yulPhaser/GeneticAlgorithms.cpp
../tools/yulPhaser/Population.cpp
../tools/yulPhaser/Program.cpp
../tools/yulPhaser/Selections.cpp
../tools/yulPhaser/SimulationRNG.cpp
)
detect_stray_source_files("${yul_phaser_sources}" "yulPhaser/")

View File

@ -53,12 +53,16 @@ ExecutionFramework::ExecutionFramework(langutil::EVMVersion _evmVersion):
m_optimiserSettings = solidity::frontend::OptimiserSettings::full();
else if (solidity::test::CommonOptions::get().optimize)
m_optimiserSettings = solidity::frontend::OptimiserSettings::standard();
m_evmHost->reset();
reset();
}
void ExecutionFramework::reset()
{
m_evmHost->reset();
for (size_t i = 0; i < 10; i++)
m_evmHost->accounts[EVMHost::convertToEVMC(account(i))].balance =
EVMHost::convertToEVMC(u256(1) << 100);
}
std::pair<bool, string> ExecutionFramework::compareAndCreateMessage(

View File

@ -251,6 +251,8 @@ private:
}
protected:
void reset();
void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0);
void sendEther(Address const& _to, u256 const& _value);
size_t currentTimestamp();

View File

@ -15,6 +15,7 @@
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <test/Common.h>
#include <test/TestCase.h>
#include <libsolutil/StringUtils.h>
@ -52,14 +53,18 @@ bool TestCase::isTestFilename(boost::filesystem::path const& _filename)
!boost::starts_with(_filename.string(), ".");
}
bool TestCase::validateSettings(langutil::EVMVersion)
void TestCase::validateSettings()
{
if (!m_settings.empty())
throw runtime_error(
"Unknown setting(s): " +
util::joinHumanReadable(m_settings | boost::adaptors::map_keys)
);
return true;
}
bool TestCase::shouldRun()
{
return m_shouldRun;
}
pair<map<string, string>, size_t> TestCase::parseSourcesAndSettingsWithLineNumbers(istream& _stream)
@ -157,20 +162,19 @@ void TestCase::expect(string::iterator& _it, string::iterator _end, string::valu
++_it;
}
bool EVMVersionRestrictedTestCase::validateSettings(langutil::EVMVersion _evmVersion)
void EVMVersionRestrictedTestCase::validateSettings()
{
if (!m_settings.count("EVMVersion"))
return true;
return;
string versionString = m_settings["EVMVersion"];
m_validatedSettings["EVMVersion"] = versionString;
m_settings.erase("EVMVersion");
if (!TestCase::validateSettings(_evmVersion))
return false;
TestCase::validateSettings();
if (versionString.empty())
return true;
return;
string comparator;
size_t versionBegin = 0;
@ -188,18 +192,23 @@ bool EVMVersionRestrictedTestCase::validateSettings(langutil::EVMVersion _evmVer
if (!version)
BOOST_THROW_EXCEPTION(runtime_error{"Invalid EVM version: \"" + versionString + "\""});
langutil::EVMVersion evmVersion = solidity::test::CommonOptions::get().evmVersion();
bool comparisonResult;
if (comparator == ">")
return _evmVersion > version;
comparisonResult = evmVersion > version;
else if (comparator == ">=")
return _evmVersion >= version;
comparisonResult = evmVersion >= version;
else if (comparator == "<")
return _evmVersion < version;
comparisonResult = evmVersion < version;
else if (comparator == "<=")
return _evmVersion <= version;
comparisonResult = evmVersion <= version;
else if (comparator == "=")
return _evmVersion == version;
comparisonResult = evmVersion == version;
else if (comparator == "!")
return !(_evmVersion == version);
comparisonResult = !(evmVersion == version);
else
BOOST_THROW_EXCEPTION(runtime_error{"Invalid EVM comparator: \"" + comparator + "\""});
if (!comparisonResult)
m_shouldRun = false;
}

View File

@ -70,10 +70,12 @@ public:
/// Validates the settings, i.e. moves them from m_settings to m_validatedSettings.
/// Throws a runtime exception if any setting is left at this class (i.e. unknown setting).
virtual void validateSettings();
/// Returns true, if the test case is supported in the current environment and false
/// otherwise which causes this test to be skipped.
/// This might check e.g. for restrictions on the EVM version.
virtual bool validateSettings(langutil::EVMVersion /*_evmVersion*/);
bool shouldRun();
protected:
std::pair<std::map<std::string, std::string>, std::size_t> parseSourcesAndSettingsWithLineNumbers(std::istream& _file);
@ -102,13 +104,14 @@ protected:
std::map<std::string, std::string> m_settings;
/// Updated settings after validation.
std::map<std::string, std::string> m_validatedSettings;
bool m_shouldRun = true;
};
class EVMVersionRestrictedTestCase: public TestCase
{
public:
/// Returns true, if the test case is supported for EVM version @arg _evmVersion, false otherwise.
bool validateSettings(langutil::EVMVersion _evmVersion) override;
void validateSettings() override;
};
}

View File

@ -94,7 +94,8 @@ int registerTests(
{
stringstream errorStream;
auto testCase = _testCaseCreator(config);
if (testCase->validateSettings(solidity::test::CommonOptions::get().evmVersion()))
testCase->validateSettings();
if (testCase->shouldRun())
switch (testCase->run(errorStream))
{
case TestCase::TestResult::Success:

View File

@ -33,7 +33,19 @@ set -e
REPO_ROOT=$(cd $(dirname "$0")/.. && pwd)
SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-build}
source "${REPO_ROOT}/scripts/common.sh"
case "$OSTYPE" in
msys)
SOLC="$REPO_ROOT/${SOLIDITY_BUILD_DIR}/solc/Release/solc.exe"
# prevents msys2 path translation for a remapping test
export MSYS2_ARG_CONV_EXCL="="
;;
*)
SOLC="$REPO_ROOT/${SOLIDITY_BUILD_DIR}/solc/solc"
;;
esac
INTERACTIVE=true
if ! tty -s || [ "$CI" ]
then

View File

@ -0,0 +1 @@
0

View File

@ -0,0 +1,10 @@
{
"language": "Solidity",
"sources":
{
"":
{
"content": "pragma solidity >=0.0; import {A} from \".\";"
}
}
}

View File

@ -0,0 +1,4 @@
{"errors":[{"component":"general","formattedMessage":":1:24: DeclarationError: Declaration \"A\" not found in \"\" (referenced as \".\").
pragma solidity >=0.0; import {A} from \".\";
^------------------^
","message":"Declaration \"A\" not found in \"\" (referenced as \".\").","severity":"error","type":"DeclarationError"}],"sources":{}}

View File

@ -14,7 +14,7 @@
sstore
/* \"A\":0:42 */
pop
","bytecode":{"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":""}},"ir":"object \"object\" {
","bytecode":{"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":"sourceMap removed"}},"ir":"object \"object\" {
code {
let x := mload(0)
sstore(add(x, 0), 0)

View File

@ -13,7 +13,7 @@
pop
stop
data_4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45 616263
","bytecode":{"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":""}},"ir":"object \"NamedObject\" {
","bytecode":{"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":"sourceMap removed"}},"ir":"object \"NamedObject\" {
code {
let x := dataoffset(\"DataName\")
sstore(add(x, 0), 0)

View File

@ -22,7 +22,7 @@ sub_0: assembly {
/* \"A\":137:149 */
revert
}
","bytecode":{"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":""}},"ir":"object \"NamedObject\" {
","bytecode":{"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":"sourceMap removed"}},"ir":"object \"NamedObject\" {
code {
let x := dataoffset(\"DataName\")
sstore(add(x, 0), 0)

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