mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #4 from chriseth/publishalpinestatic
Publishalpinestatic
This commit is contained in:
commit
c71b96051b
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,5 +1,5 @@
|
||||
.commit_hash.txt
|
||||
.prerelease.txt
|
||||
commit_hash.txt
|
||||
prerelease.txt
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
|
||||
138
.travis.yml
138
.travis.yml
@ -21,10 +21,11 @@
|
||||
# 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 solidity contributors.
|
||||
# (c) 2016-2017 solidity contributors.
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
language: cpp
|
||||
|
||||
branches:
|
||||
# We need to whitelist the branches which we want to have "push" automation,
|
||||
# this includes tags (which are treated as branches by travis).
|
||||
@ -33,6 +34,18 @@ branches:
|
||||
- develop
|
||||
- release
|
||||
- /^v[0-9]/
|
||||
|
||||
env:
|
||||
global:
|
||||
- ENCRYPTION_LABEL="6d4541b72666"
|
||||
- SOLC_BUILD_TYPE=RelWithDebInfo
|
||||
- SOLC_DOCS=Off
|
||||
- SOLC_EMSCRIPTEN=Off
|
||||
- SOLC_INSTALL_DEPS_TRAVIS=On
|
||||
- SOLC_RELEASE=On
|
||||
- SOLC_TESTS=On
|
||||
- SOLC_DOCKER=Off
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# Ubuntu 14.04 LTS "Trusty Tahr"
|
||||
@ -56,6 +69,38 @@ matrix:
|
||||
env:
|
||||
- ZIP_SUFFIX=ubuntu-trusty-clang
|
||||
|
||||
# Docker target, which generates a statically linked alpine image
|
||||
- os: linux
|
||||
dist: trusty
|
||||
sudo: required
|
||||
services:
|
||||
- docker
|
||||
env:
|
||||
- SOLC_DOCKER=On
|
||||
- SOLC_INSTALL_DEPS_TRAVIS=Off
|
||||
- SOLC_RELEASE=Off
|
||||
- SOLC_TESTS=Off
|
||||
|
||||
# Emscripten target, which compiles 'solc' to javascript and uploads the resulting .js
|
||||
# files to https://github.com/ethereum/solc-bin. These binaries are used in Browser-Solidity
|
||||
# and in other Ethereum web-based development contexts.
|
||||
- os: linux
|
||||
dist: trusty
|
||||
sudo: required
|
||||
compiler: gcc
|
||||
node_js:
|
||||
- "6"
|
||||
services:
|
||||
- docker
|
||||
before_install:
|
||||
- nvm install 6
|
||||
- nvm use 6
|
||||
- docker pull trzeci/emscripten:sdk-tag-1.35.4-64bit
|
||||
env:
|
||||
- SOLC_EMSCRIPTEN=On
|
||||
- SOLC_INSTALL_DEPS_TRAVIS=Off
|
||||
- SOLC_RELEASE=Off
|
||||
- SOLC_TESTS=Off
|
||||
# OS X Mavericks (10.9)
|
||||
# https://en.wikipedia.org/wiki/OS_X_Mavericks
|
||||
#
|
||||
@ -73,7 +118,7 @@ matrix:
|
||||
# env:
|
||||
# # Workaround for "macOS - Yosemite, El Capitan and Sierra hanging?"
|
||||
# # https://github.com/ethereum/solidity/issues/894
|
||||
# - TRAVIS_TESTS=Off
|
||||
# - SOLC_TESTS=Off
|
||||
# - ZIP_SUFFIX=osx-yosemite
|
||||
|
||||
# OS X El Capitan (10.11)
|
||||
@ -84,10 +129,10 @@ matrix:
|
||||
# env:
|
||||
# # The use of Debug config here ONLY for El Capitan is a workaround for "The Heisenbug"
|
||||
# # See https://github.com/ethereum/webthree-umbrella/issues/565
|
||||
# - TRAVIS_BUILD_TYPE=Debug
|
||||
# - SOLC_BUILD_TYPE=Debug
|
||||
# # Workaround for "macOS - Yosemite, El Capitan and Sierra hanging?"
|
||||
# # https://github.com/ethereum/solidity/issues/894
|
||||
# - TRAVIS_TESTS=Off
|
||||
# - SOLC_TESTS=Off
|
||||
# - ZIP_SUFFIX=osx-elcapitan
|
||||
|
||||
# macOS Sierra (10.12)
|
||||
@ -98,10 +143,10 @@ matrix:
|
||||
# env:
|
||||
# # Look like "The Heisenbug" is occurring here too, so we'll do the same workaround.
|
||||
# # See https://travis-ci.org/ethereum/solidity/jobs/150240930
|
||||
# - TRAVIS_BUILD_TYPE=Debug
|
||||
# - SOLC_BUILD_TYPE=Debug
|
||||
# # Workaround for "macOS - Yosemite, El Capitan and Sierra hanging?"
|
||||
# # https://github.com/ethereum/solidity/issues/894
|
||||
# - TRAVIS_TESTS=Off
|
||||
# - SOLC_TESTS=Off
|
||||
# - ZIP_SUFFIX=macos-sierra
|
||||
|
||||
git:
|
||||
@ -112,42 +157,28 @@ cache:
|
||||
directories:
|
||||
- boost_1_57_0
|
||||
- build
|
||||
- $HOME/.local
|
||||
|
||||
install:
|
||||
- test $TRAVIS_INSTALL_DEPS != On || ./scripts/install_deps.sh
|
||||
- test $SOLC_INSTALL_DEPS_TRAVIS != On || (scripts/install_deps.sh)
|
||||
- test "$TRAVIS_OS_NAME" != "linux" || (scripts/install_cmake.sh)
|
||||
- echo -n "$TRAVIS_COMMIT" > commit_hash.txt
|
||||
before_script:
|
||||
- test $TRAVIS_EMSCRIPTEN != On || ./scripts/build_emscripten.sh
|
||||
- test $TRAVIS_RELEASE != On || (mkdir -p build
|
||||
&& cd build
|
||||
&& cmake .. -DCMAKE_BUILD_TYPE=$TRAVIS_BUILD_TYPE
|
||||
&& make -j2
|
||||
&& cd ..
|
||||
&& ./scripts/release.sh $ZIP_SUFFIX
|
||||
&& ./scripts/create_source_tarball.sh )
|
||||
script:
|
||||
- test $TRAVIS_DOCS != On || ./scripts/docs.sh
|
||||
- test $SOLC_DOCKER != On || (
|
||||
docker build -t tmp -f scripts/Dockerfile .
|
||||
tmp_container=$(docker create tmp sh)
|
||||
mkdir -p upload
|
||||
docker cp ${tmp_container}:/usr/bin/solc upload/
|
||||
)
|
||||
|
||||
# There are a variety of reliability issues with the Solidity unit-tests at the time of
|
||||
# writing (especially on macOS), so within TravisCI we will try to run the unit-tests
|
||||
# up to 3 times before giving up and declaring the tests as broken.
|
||||
#
|
||||
# We should aim to remove this "retry logic" as soon as we can, because it is a
|
||||
# band-aid for issues which need solving at their root. Some of those issues will be
|
||||
# in Solidity's RPC setup and some will be in 'eth'. It seems unlikely that Solidity
|
||||
# itself is broken from the failure messages which we are seeing.
|
||||
#
|
||||
# More details on known issues at https://github.com/ethereum/solidity/issues/769
|
||||
- test $TRAVIS_TESTS != On || (cd $TRAVIS_BUILD_DIR && (./scripts/tests.sh || ./scripts/tests.sh || ./scripts/tests.sh) )
|
||||
env:
|
||||
global:
|
||||
- ENCRYPTION_LABEL="6d4541b72666"
|
||||
- TRAVIS_BUILD_TYPE=RelWithDebInfo
|
||||
- TRAVIS_DOCS=Off
|
||||
- TRAVIS_EMSCRIPTEN=Off
|
||||
- TRAVIS_INSTALL_DEPS=On
|
||||
- TRAVIS_RELEASE=On
|
||||
- TRAVIS_TESTS=On
|
||||
before_script:
|
||||
- test $SOLC_EMSCRIPTEN != On || (scripts/build_emscripten.sh)
|
||||
- test $SOLC_RELEASE != On || (scripts/build.sh $SOLC_BUILD_TYPE
|
||||
&& scripts/release.sh $ZIP_SUFFIX
|
||||
&& scripts/create_source_tarball.sh)
|
||||
|
||||
script:
|
||||
- test $SOLC_DOCS != On || (scripts/docs.sh)
|
||||
- test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && scripts/tests.sh)
|
||||
|
||||
deploy:
|
||||
# This is the deploy target for the Emscripten build.
|
||||
@ -156,14 +187,24 @@ deploy:
|
||||
# Both the build and deploy steps for Emscripten are only run within the Ubuntu
|
||||
# configurations (not for macOS). That is controlled by conditionals within the bash
|
||||
# scripts because TravisCI doesn't provide much in the way of conditional logic.
|
||||
- provider: script
|
||||
script: test $TRAVIS_EMSCRIPTEN != On || scripts/release_emscripten.sh
|
||||
skip_cleanup: true
|
||||
on:
|
||||
branch:
|
||||
- develop
|
||||
- release
|
||||
|
||||
|
||||
# - provider: script
|
||||
# script: test $SOLC_EMSCRIPTEN != On || (scripts/release_emscripten.sh)
|
||||
# skip_cleanup: true
|
||||
# on:
|
||||
# branch:
|
||||
# - develop
|
||||
# - release
|
||||
# # This is the deploy target for the dockerfile. If we are pushing into a develop branch, it will be tagged
|
||||
# # as a nightly and appended the commit of the branch it was pushed in. If we are pushing to master it will
|
||||
# # be tagged as "stable" and given the version tag as well.
|
||||
# - provider: script
|
||||
# script: test $SOLC_DOCKER != On || (scripts/docker_deploy.sh)
|
||||
# skip_cleanup: true
|
||||
# on:
|
||||
# branch:
|
||||
# - develop
|
||||
# - release
|
||||
# This is the deploy target for the native build (Linux and macOS)
|
||||
# which generates ZIPs per commit and the source tarball.
|
||||
#
|
||||
@ -175,11 +216,8 @@ deploy:
|
||||
|
||||
overwrite: true
|
||||
file_glob: true
|
||||
file:
|
||||
- $TRAVIS_BUILD_DIR/solidity*.zip
|
||||
- $TRAVIS_BUILD_DIR/solidity*tar.gz
|
||||
file: $TRAVIS_BUILD_DIR/upload/*
|
||||
skip_cleanup: true
|
||||
on:
|
||||
all_branches: true
|
||||
tags: true
|
||||
condition: $TRAVIS_RELEASE == On
|
||||
|
||||
@ -8,7 +8,7 @@ include(EthPolicy)
|
||||
eth_policy()
|
||||
|
||||
# project name and version should be set after cmake_policy CMP0048
|
||||
set(PROJECT_VERSION "0.4.8")
|
||||
set(PROJECT_VERSION "0.4.11")
|
||||
project(solidity VERSION ${PROJECT_VERSION})
|
||||
|
||||
# Let's find our dependencies
|
||||
|
||||
65
Changelog.md
65
Changelog.md
@ -1,7 +1,68 @@
|
||||
### 0.4.8 (unreleased)
|
||||
### 0.4.11 (unreleased)
|
||||
|
||||
BugFixes:
|
||||
### 0.4.10 (2017-03-15)
|
||||
|
||||
Features:
|
||||
* Add ``assert(condition)``, which throws if condition is false (meant for internal errors).
|
||||
* Add ``require(condition)``, which throws if condition is false (meant for invalid input).
|
||||
* Commandline interface: Do not overwrite files unless forced.
|
||||
* Introduce ``.transfer(value)`` for sending Ether.
|
||||
* Code generator: Support ``revert()`` to abort with rolling back, but not consuming all gas.
|
||||
* Inline assembly: Support ``revert`` (EIP140) as an opcode.
|
||||
* Parser: Support scientific notation in numbers (e.g. ``2e8`` and ``200e-2``).
|
||||
* Type system: Support explicit conversion of external function to address.
|
||||
* Type system: Warn if base of exponentiation is literal (result type might be unexpected).
|
||||
* Type system: Warn if constant state variables are not compile-time constants.
|
||||
|
||||
Bugfixes:
|
||||
* Commandline interface: Always escape filenames (replace ``/``, ``:`` and ``.`` with ``_``).
|
||||
* Commandline interface: Do not try creating paths ``.`` and ``..``.
|
||||
* Commandline interface: Allow long library names.
|
||||
* Parser: Disallow octal literals.
|
||||
* Type system: Fix a crash caused by continuing on fatal errors in the code.
|
||||
* Type system: Disallow compound assignment for tuples.
|
||||
* Type system: Detect cyclic dependencies between constants.
|
||||
* Type system: Disallow arrays with negative length.
|
||||
* Type system: Fix a crash related to invalid binary operators.
|
||||
* Type system: Disallow ``var`` declaration with empty tuple type.
|
||||
* Type system: Correctly convert function argument types to pointers for member functions.
|
||||
* Type system: Move privateness of constructor into AST itself.
|
||||
* Inline assembly: Charge one stack slot for non-value types during analysis.
|
||||
* Assembly output: Print source location before the operation it refers to instead of after.
|
||||
* Optimizer: Stop trying to optimize tricky constants after a while.
|
||||
|
||||
### 0.4.9 (2017-01-31)
|
||||
|
||||
Features:
|
||||
* Compiler interface: Contracts and libraries can be referenced with a ``file:`` prefix to make them unique.
|
||||
* Compiler interface: Report source location for "stack too deep" errors.
|
||||
* AST: Use deterministic node identifiers.
|
||||
* Inline assembly: introduce ``invalid`` (EIP141) as an opcode.
|
||||
* Type system: Introduce type identifier strings.
|
||||
* Type checker: Warn about invalid checksum for addresses and deduce type from valid ones.
|
||||
* Metadata: Do not include platform in the version number.
|
||||
* Metadata: Add option to store sources as literal content.
|
||||
* Code generator: Extract array utils into low-level functions.
|
||||
* Code generator: Internal errors (array out of bounds, etc.) now cause a reversion by using an invalid
|
||||
instruction (0xfe - EIP141) instead of an invalid jump. Invalid jump is still kept for explicit throws.
|
||||
|
||||
Bugfixes:
|
||||
* Code generator: Allow recursive structs.
|
||||
* Inline assembly: Disallow variables named like opcodes.
|
||||
* Type checker: Allow multiple events of the same name (but with different arities or argument types)
|
||||
* Natspec parser: Fix error with ``@param`` parsing and whitespace.
|
||||
|
||||
### 0.4.8 (2017-01-13)
|
||||
|
||||
Features:
|
||||
* Optimiser: Performance improvements.
|
||||
* Output: Print assembly in new standardized Solidity assembly format.
|
||||
|
||||
Bugfixes:
|
||||
* Remappings: Prefer longer context over longer prefix.
|
||||
* Type checker, code generator: enable access to events of base contracts' names.
|
||||
* Imports: ``import ".dir/a"`` is not a relative path. Relative paths begin with directory ``.`` or ``..``.
|
||||
* Type checker, disallow inheritances of different kinds (e.g. a function and a modifier) of members of the same name
|
||||
|
||||
### 0.4.7 (2016-12-15)
|
||||
|
||||
|
||||
@ -62,7 +62,7 @@ test_script:
|
||||
- ps: Start-Sleep -s 100
|
||||
- cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION%
|
||||
- copy "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x86\Microsoft.VC140.CRT\msvc*.dll" .
|
||||
- soltest.exe -- --ipcpath \\.\pipe\geth.ipc
|
||||
- soltest.exe --show-progress -- --ipcpath \\.\pipe\geth.ipc
|
||||
|
||||
artifacts:
|
||||
- path: solidity-windows.zip
|
||||
|
||||
@ -71,7 +71,7 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA
|
||||
add_compile_options(-fPIC)
|
||||
|
||||
# Configuration-specific compiler settings.
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-Og -g -DETH_DEBUG")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DETH_DEBUG")
|
||||
set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g")
|
||||
|
||||
@ -60,6 +60,8 @@ if (SOL_COMMIT_HASH AND SOL_LOCAL_CHANGES)
|
||||
set(SOL_COMMIT_HASH "${SOL_COMMIT_HASH}.mod")
|
||||
endif()
|
||||
|
||||
set(SOL_VERSION_COMMIT "commit.${SOL_COMMIT_HASH}")
|
||||
set(SOl_VERSION_PLATFORM ETH_BUILD_PLATFORM)
|
||||
set(SOL_VERSION_BUILDINFO "commit.${SOL_COMMIT_HASH}.${ETH_BUILD_PLATFORM}")
|
||||
|
||||
set(TMPFILE "${ETH_DST_DIR}/BuildInfo.h.tmp")
|
||||
|
||||
@ -8,3 +8,5 @@
|
||||
#define ETH_BUILD_PLATFORM "@ETH_BUILD_PLATFORM@"
|
||||
#define SOL_VERSION_PRERELEASE "@SOL_VERSION_PRERELEASE@"
|
||||
#define SOL_VERSION_BUILDINFO "@SOL_VERSION_BUILDINFO@"
|
||||
#define SOL_VERSION_COMMIT "@SOL_VERSION_COMMIT@"
|
||||
#define SOL_VERSION_PLATFORM "@SOL_VERSION_PLATFORM@"
|
||||
|
||||
1023
docs/assembly.rst
Normal file
1023
docs/assembly.rst
Normal file
File diff suppressed because it is too large
Load Diff
@ -23,7 +23,7 @@ contract in order to become the "richest", inspired by
|
||||
`King of the Ether <https://www.kingoftheether.com/>`_.
|
||||
|
||||
In the following contract, if you are usurped as the richest,
|
||||
you will recieve the funds of the person who has gone on to
|
||||
you will receive the funds of the person who has gone on to
|
||||
become the new richest.
|
||||
|
||||
::
|
||||
@ -81,7 +81,7 @@ This is as opposed to the more intuitive sending pattern:
|
||||
mostSent = msg.value;
|
||||
}
|
||||
|
||||
function becomeRichest() returns (bool) {
|
||||
function becomeRichest() payable returns (bool) {
|
||||
if (msg.value > mostSent) {
|
||||
// Check if call succeeds to prevent an attacker
|
||||
// from trapping the previous person's funds in
|
||||
|
||||
12
docs/conf.py
12
docs/conf.py
@ -15,6 +15,7 @@
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
@ -49,16 +50,21 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'Solidity'
|
||||
copyright = '2016, Ethereum'
|
||||
copyright = '2016-2017, Ethereum'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.4.8'
|
||||
with open('../CMakeLists.txt', 'r') as f:
|
||||
version = re.search('PROJECT_VERSION "([^"]+)"', f.read()).group(1)
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.4.8-develop'
|
||||
if os.path.isfile('../prerelease.txt') != True or os.path.getsize('../prerelease.txt') == 0:
|
||||
release = version
|
||||
else:
|
||||
# This is a prerelease version
|
||||
release = version + '-develop'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
@ -145,11 +145,11 @@ This means that cyclic creation dependencies are impossible.
|
||||
|
||||
.. index:: ! visibility, external, public, private, internal
|
||||
|
||||
.. _visibility-and-accessors:
|
||||
.. _visibility-and-getters:
|
||||
|
||||
************************
|
||||
Visibility and Accessors
|
||||
************************
|
||||
**********************
|
||||
Visibility and Getters
|
||||
**********************
|
||||
|
||||
Since Solidity knows two kinds of function calls (internal
|
||||
ones that do not create an actual EVM call (also called
|
||||
@ -173,7 +173,7 @@ and the default is ``internal``.
|
||||
``public``:
|
||||
Public functions are part of the contract
|
||||
interface and can be either called internally or via
|
||||
messages. For public state variables, an automatic accessor
|
||||
messages. For public state variables, an automatic getter
|
||||
function (see below) is generated.
|
||||
|
||||
``internal``:
|
||||
@ -243,12 +243,12 @@ In the following example, ``D``, can call ``c.getData()`` to retrieve the value
|
||||
}
|
||||
}
|
||||
|
||||
.. index:: ! accessor;function, ! function;accessor
|
||||
.. index:: ! getter;function, ! function;getter
|
||||
|
||||
Accessor Functions
|
||||
==================
|
||||
Getter Functions
|
||||
================
|
||||
|
||||
The compiler automatically creates accessor functions for
|
||||
The compiler automatically creates getter functions for
|
||||
all **public** state variables. For the contract given below, the compiler will
|
||||
generate a function called ``data`` that does not take any
|
||||
arguments and returns a ``uint``, the value of the state
|
||||
@ -271,7 +271,7 @@ be done at declaration.
|
||||
}
|
||||
}
|
||||
|
||||
The accessor functions have external visibility. If the
|
||||
The getter functions have external visibility. If the
|
||||
symbol is accessed internally (i.e. without ``this.``),
|
||||
it is evaluated as a state variable and if it is accessed externally
|
||||
(i.e. with ``this.``), it is evaluated as a function.
|
||||
@ -428,8 +428,25 @@ change by overriding).
|
||||
Constant State Variables
|
||||
************************
|
||||
|
||||
State variables can be declared as constant (this is not yet implemented
|
||||
for array and struct types and not possible for mapping types).
|
||||
State variables can be declared as ``constant``. In this case, they have to be
|
||||
assigned from an expression which is a constant at compile time. Any expression
|
||||
that accesses storage, blockchain data (e.g. ``now``, ``this.balance`` or
|
||||
``block.number``) or
|
||||
execution data (``msg.gas``) or make calls to external contracts are disallowed. Expressions
|
||||
that might have a side-effect on memory allocation are allowed, but those that
|
||||
might have a side-effect on other memory objects are not. The built-in functions
|
||||
``keccak256``, ``sha256``, ``ripemd160``, ``ecrecover``, ``addmod`` and ``mulmod``
|
||||
are allowed (ever though they do call external contracts).
|
||||
|
||||
The reason behind allowing side-effects on the memory allocator is that it
|
||||
should be possible to construct complex objects like e.g. lookup-tables.
|
||||
This feature is not yet fully usable.
|
||||
|
||||
The compiler does not reserve a storage slot for these variables and every occurrence is
|
||||
replaced by the respective constant expression (which might be computed to a single value by the optimizer).
|
||||
|
||||
Not all types for constants are implemented at this time. The only supported types are
|
||||
value types and strings.
|
||||
|
||||
::
|
||||
|
||||
@ -438,12 +455,9 @@ for array and struct types and not possible for mapping types).
|
||||
contract C {
|
||||
uint constant x = 32**22 + 8;
|
||||
string constant text = "abc";
|
||||
bytes32 constant myHash = keccak256("abc");
|
||||
}
|
||||
|
||||
This has the effect that the compiler does not reserve a storage slot
|
||||
for these variables and every occurrence is replaced by their constant value.
|
||||
|
||||
The value expression can only contain integer arithmetics.
|
||||
|
||||
******************
|
||||
Constant Functions
|
||||
@ -462,7 +476,7 @@ Functions can be declared constant. These functions promise not to modify the st
|
||||
}
|
||||
|
||||
.. note::
|
||||
Accessor methods are marked constant.
|
||||
Getter methods are marked constant.
|
||||
|
||||
.. warning::
|
||||
The compiler does not enforce yet that a constant method is not modifying state.
|
||||
@ -877,6 +891,13 @@ cannot be resolved.
|
||||
A simple rule to remember is to specify the base classes in
|
||||
the order from "most base-like" to "most derived".
|
||||
|
||||
Inheriting Different Kinds of Members of the Same Name
|
||||
======================================================
|
||||
|
||||
When the inheritance results in a contract with a function and a modifier of the same name, it is considered as an error.
|
||||
This error is produced also by an event and a modifier of the same name, and a function and an event of the same name.
|
||||
As an exception, a state variable getter can override a public function.
|
||||
|
||||
.. index:: ! contract;abstract, ! abstract contract
|
||||
|
||||
******************
|
||||
@ -1080,7 +1101,7 @@ Restrictions for libraries in comparison to contracts:
|
||||
|
||||
- No state variables
|
||||
- Cannot inherit nor be inherited
|
||||
- Cannot recieve Ether
|
||||
- Cannot receive Ether
|
||||
|
||||
(These might be lifted at a later point.)
|
||||
|
||||
|
||||
@ -104,7 +104,7 @@ contract can be called internally.
|
||||
External Function Calls
|
||||
-----------------------
|
||||
|
||||
The expressions ``this.g(8);`` and ``c.g(2);`` (where ``g`` is a contract
|
||||
The expressions ``this.g(8);`` and ``c.g(2);`` (where ``c`` is a contract
|
||||
instance) are also valid function calls, but this time, the function
|
||||
will be called "externally", via a message call and not directly via jumps.
|
||||
Please note that function calls on ``this`` cannot be used in the constructor, as the
|
||||
@ -113,8 +113,8 @@ actual contract has not been created yet.
|
||||
Functions of other contracts have to be called externally. For an external call,
|
||||
all function arguments have to be copied to memory.
|
||||
|
||||
When calling functions
|
||||
of other contracts, the amount of Wei sent with the call and the gas can be specified::
|
||||
When calling functions of other contracts, the amount of Wei sent with the call and
|
||||
the gas can be specified with special options ``.value()`` and ``.gas()``, respectively::
|
||||
|
||||
contract InfoFeed {
|
||||
function info() payable returns (uint ret) { return 42; }
|
||||
@ -127,8 +127,8 @@ of other contracts, the amount of Wei sent with the call and the gas can be spec
|
||||
function callFeed() { feed.info.value(10).gas(800)(); }
|
||||
}
|
||||
|
||||
The modifier ``payable`` has to be used for ``info``, because otherwise,
|
||||
we would not be able to send Ether to it in the call ``feed.info.value(10).gas(800)()``.
|
||||
The modifier ``payable`` has to be used for ``info``, because otherwise, the `.value()`
|
||||
option would not be available.
|
||||
|
||||
Note that the expression ``InfoFeed(addr)`` performs an explicit type conversion stating
|
||||
that "we know that the type of the contract at the given address is ``InfoFeed``" and
|
||||
@ -235,7 +235,7 @@ creation-dependencies are not possible.
|
||||
}
|
||||
}
|
||||
|
||||
As seen in the example, it is possible to forward Ether to the creation,
|
||||
As seen in the example, it is possible to forward Ether to the creation using the ``.value()`` option,
|
||||
but it is not possible to limit the amount of gas. If the creation fails
|
||||
(due to out-of-stack, not enough balance or other problems), an exception
|
||||
is thrown.
|
||||
@ -384,489 +384,32 @@ In the following example, we show how ``throw`` can be used to easily revert an
|
||||
|
||||
Currently, Solidity automatically generates a runtime exception in the following situations:
|
||||
|
||||
1. If you access an array at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``).
|
||||
1. If you access a fixed-length ``bytesN`` at a too large or negative index.
|
||||
1. If you call a function via a message call but it does not finish properly (i.e. it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall`` or ``callcode`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``.
|
||||
1. If you create a contract using the ``new`` keyword but the contract creation does not finish properly (see above for the definition of "not finish properly").
|
||||
1. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``).
|
||||
1. If you shift by a negative amount.
|
||||
1. If you convert a value too big or negative into an enum type.
|
||||
1. If you perform an external function call targeting a contract that contains no code.
|
||||
1. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function).
|
||||
1. If your contract receives Ether via a public accessor function.
|
||||
|
||||
Internally, Solidity performs an "invalid jump" when an exception is thrown and thus causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction (or at least call) without effect.
|
||||
|
||||
.. index:: ! assembly, ! asm, ! evmasm
|
||||
|
||||
Inline Assembly
|
||||
===============
|
||||
|
||||
For more fine-grained control especially in order to enhance the language by writing libraries,
|
||||
it is possible to interleave Solidity statements with inline assembly in a language close
|
||||
to the one of the virtual machine. Due to the fact that the EVM is a stack machine, it is
|
||||
often hard to address the correct stack slot and provide arguments to opcodes at the correct
|
||||
point on the stack. Solidity's inline assembly tries to facilitate that and other issues
|
||||
arising when writing manual assembly by the following features:
|
||||
|
||||
* functional-style opcodes: ``mul(1, add(2, 3))`` instead of ``push1 3 push1 2 add push1 1 mul``
|
||||
* assembly-local variables: ``let x := add(2, 3) let y := mload(0x40) x := add(x, y)``
|
||||
* access to external variables: ``function f(uint x) { assembly { x := sub(x, 1) } }``
|
||||
* labels: ``let x := 10 repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))``
|
||||
|
||||
We now want to describe the inline assembly language in detail.
|
||||
|
||||
.. warning::
|
||||
Inline assembly is a way to access the Ethereum Virtual Machine
|
||||
at a low level. This discards several important safety
|
||||
features of Solidity.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
The following example provides library code to access the code of another contract and
|
||||
load it into a ``bytes`` variable. This is not possible at all with "plain Solidity" and the
|
||||
idea is that assembly libraries will be used to enhance the language in such ways.
|
||||
|
||||
.. code::
|
||||
|
||||
pragma solidity ^0.4.0;
|
||||
|
||||
library GetCode {
|
||||
function at(address _addr) returns (bytes o_code) {
|
||||
assembly {
|
||||
// retrieve the size of the code, this needs assembly
|
||||
let size := extcodesize(_addr)
|
||||
// allocate output byte array - this could also be done without assembly
|
||||
// by using o_code = new bytes(size)
|
||||
o_code := mload(0x40)
|
||||
// new "memory end" including padding
|
||||
mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
|
||||
// store length in memory
|
||||
mstore(o_code, size)
|
||||
// actually retrieve the code, this needs assembly
|
||||
extcodecopy(_addr, add(o_code, 0x20), 0, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Inline assembly could also be beneficial in cases where the optimizer fails to produce
|
||||
efficient code. Please be aware that assembly is much more difficult to write because
|
||||
the compiler does not perform checks, so you should use it for complex things only if
|
||||
you really know what you are doing.
|
||||
|
||||
.. code::
|
||||
|
||||
pragma solidity ^0.4.0;
|
||||
|
||||
library VectorSum {
|
||||
// This function is less efficient because the optimizer currently fails to
|
||||
// remove the bounds checks in array access.
|
||||
function sumSolidity(uint[] _data) returns (uint o_sum) {
|
||||
for (uint i = 0; i < _data.length; ++i)
|
||||
o_sum += _data[i];
|
||||
}
|
||||
|
||||
// We know that we only access the array in bounds, so we can avoid the check.
|
||||
// 0x20 needs to be added to an array because the first slot contains the
|
||||
// array length.
|
||||
function sumAsm(uint[] _data) returns (uint o_sum) {
|
||||
for (uint i = 0; i < _data.length; ++i) {
|
||||
assembly {
|
||||
o_sum := mload(add(add(_data, 0x20), i))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Syntax
|
||||
------
|
||||
|
||||
Inline assembly parses comments, literals and identifiers exactly as Solidity, so you can use the
|
||||
usual ``//`` and ``/* */`` comments. Inline assembly is initiated by ``assembly { ... }`` and inside
|
||||
these curly braces, the following can be used (see the later sections for more details)
|
||||
|
||||
- literals, e.g. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters)
|
||||
- opcodes (in "instruction style"), e.g. ``mload sload dup1 sstore``, for a list see below
|
||||
- opcodes in functional style, e.g. ``add(1, mload(0))``
|
||||
- labels, e.g. ``name:``
|
||||
- variable declarations, e.g. ``let x := 7`` or ``let x := add(y, 3)``
|
||||
- identifiers (externals, labels or assembly-local variables), e.g. ``jump(name)``, ``3 x add``
|
||||
- assignments (in "instruction style"), e.g. ``3 =: x``
|
||||
- assignments in functional style, e.g. ``x := add(y, 3)``
|
||||
- blocks where local variables are scoped inside, e.g. ``{ let x := 3 { let y := add(x, 1) } }``
|
||||
|
||||
Opcodes
|
||||
-------
|
||||
|
||||
This document does not want to be a full description of the Ethereum virtual machine, but the
|
||||
following list can be used as a reference of its opcodes.
|
||||
|
||||
If an opcode takes arguments (always from the top of the stack), they are given in parentheses.
|
||||
Note that the order of arguments can be seen as being reversed compared to the instructional style (explained below).
|
||||
Opcodes marked with ``-`` do not push an item onto the stack, those marked with ``*`` are
|
||||
special and all others push exactly one item onte the stack.
|
||||
|
||||
In the following, ``mem[a...b)`` signifies the bytes of memory starting at position ``a`` up to
|
||||
(excluding) position ``b`` and ``storage[p]`` signifies the storage contents at position ``p``.
|
||||
|
||||
The opcodes ``pushi`` and ``jumpdest`` cannot be used directly.
|
||||
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| stop + `-` | stop execution, identical to return(0,0) |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| add(x, y) | | x + y |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| sub(x, y) | | x - y |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| mul(x, y) | | x * y |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| div(x, y) | | x / y |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| sdiv(x, y) | | x / y, for signed numbers in two's complement |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| mod(x, y) | | x % y |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| smod(x, y) | | x % y, for signed numbers in two's complement |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| exp(x, y) | | x to the power of y |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| not(x) | | ~x, every bit of x is negated |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| lt(x, y) | | 1 if x < y, 0 otherwise |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| gt(x, y) | | 1 if x > y, 0 otherwise |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| slt(x, y) | | 1 if x < y, 0 otherwise, for signed numbers in two's complement |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| sgt(x, y) | | 1 if x > y, 0 otherwise, for signed numbers in two's complement |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| eq(x, y) | | 1 if x == y, 0 otherwise |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| iszero(x) | | 1 if x == 0, 0 otherwise |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| and(x, y) | | bitwise and of x and y |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| or(x, y) | | bitwise or of x and y |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| xor(x, y) | | bitwise xor of x and y |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| byte(n, x) | | nth byte of x, where the most significant byte is the 0th byte |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| addmod(x, y, m) | | (x + y) % m with arbitrary precision arithmetics |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| mulmod(x, y, m) | | (x * y) % m with arbitrary precision arithmetics |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| signextend(i, x) | | sign extend from (i*8+7)th bit counting from least significant |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| sha3(p, n) | | keccak(mem[p...(p+n))) |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| jump(label) | `-` | jump to label / code position |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| jumpi(label, cond) | `-` | jump to label if cond is nonzero |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| pc | | current position in code |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| pop(x) | `-` | remove the element pushed by x |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| dup1 ... dup16 | | copy ith stack slot to the top (counting from top) |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| swap1 ... swap16 | `*` | swap topmost and ith stack slot below it |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| mload(p) | | mem[p..(p+32)) |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| mstore(p, v) | `-` | mem[p..(p+32)) := v |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| mstore8(p, v) | `-` | mem[p] := v & 0xff - only modifies a single byte |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| sload(p) | | storage[p] |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| sstore(p, v) | `-` | storage[p] := v |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| msize | | size of memory, i.e. largest accessed memory index |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| gas | | gas still available to execution |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| address | | address of the current contract / execution context |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| balance(a) | | wei balance at address a |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| caller | | call sender (excluding delegatecall) |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| callvalue | | wei sent together with the current call |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| calldataload(p) | | calldata starting from position p (32 bytes) |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| calldatasize | | size of calldata in bytes |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| calldatacopy(t, f, s) | `-` | copy s bytes from calldata at position f to mem at position t |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| codesize | | size of the code of the current contract / execution context |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| codecopy(t, f, s) | `-` | copy s bytes from code at position f to mem at position t |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| extcodesize(a) | | size of the code at address a |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| extcodecopy(a, t, f, s) | `-` | like codecopy(t, f, s) but take code at address a |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| create(v, p, s) | | create new contract with code mem[p..(p+s)) and send v wei |
|
||||
| | | and return the new address |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| call(g, a, v, in, | | call contract at address a with input mem[in..(in+insize)) |
|
||||
| insize, out, outsize) | | providing g gas and v wei and output area |
|
||||
| | | mem[out..(out+outsize)) returning 0 on error (eg. out of gas) |
|
||||
| | | and 1 on success |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| callcode(g, a, v, in, | | identical to `call` but only use the code from a and stay |
|
||||
| insize, out, outsize) | | in the context of the current contract otherwise |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| delegatecall(g, a, in, | | identical to `callcode` but also keep ``caller`` |
|
||||
| insize, out, outsize) | | and ``callvalue`` |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| return(p, s) | `-` | end execution, return data mem[p..(p+s)) |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| selfdestruct(a) | `-` | end execution, destroy current contract and send funds to a |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| log0(p, s) | `-` | log without topics and data mem[p..(p+s)) |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| log1(p, s, t1) | `-` | log with topic t1 and data mem[p..(p+s)) |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| log2(p, s, t1, t2) | `-` | log with topics t1, t2 and data mem[p..(p+s)) |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| log3(p, s, t1, t2, t3) | `-` | log with topics t1, t2, t3 and data mem[p..(p+s)) |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| log4(p, s, t1, t2, t3, | `-` | log with topics t1, t2, t3, t4 and data mem[p..(p+s)) |
|
||||
| t4) | | |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| origin | | transaction sender |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| gasprice | | gas price of the transaction |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| blockhash(b) | | hash of block nr b - only for last 256 blocks excluding current |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| coinbase | | current mining beneficiary |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| timestamp | | timestamp of the current block in seconds since the epoch |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| number | | current block number |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| difficulty | | difficulty of the current block |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
| gaslimit | | block gas limit of the current block |
|
||||
+-------------------------+------+-----------------------------------------------------------------+
|
||||
|
||||
Literals
|
||||
--------
|
||||
|
||||
You can use integer constants by typing them in decimal or hexadecimal notation and an
|
||||
appropriate ``PUSHi`` instruction will automatically be generated. The following creates code
|
||||
to add 2 and 3 resulting in 5 and then computes the bitwise and with the string "abc".
|
||||
Strings are stored left-aligned and cannot be longer than 32 bytes.
|
||||
|
||||
.. code::
|
||||
|
||||
assembly { 2 3 add "abc" and }
|
||||
|
||||
Functional Style
|
||||
-----------------
|
||||
|
||||
You can type opcode after opcode in the same way they will end up in bytecode. For example
|
||||
adding ``3`` to the contents in memory at position ``0x80`` would be
|
||||
|
||||
.. code::
|
||||
|
||||
3 0x80 mload add 0x80 mstore
|
||||
|
||||
As it is often hard to see what the actual arguments for certain opcodes are,
|
||||
Solidity inline assembly also provides a "functional style" notation where the same code
|
||||
would be written as follows
|
||||
|
||||
.. code::
|
||||
|
||||
mstore(0x80, add(mload(0x80), 3))
|
||||
|
||||
Functional style and instructional style can be mixed, but any opcode inside a
|
||||
functional style expression has to return exactly one stack slot (most of the opcodes do).
|
||||
|
||||
Note that the order of arguments is reversed in functional-style as opposed to the instruction-style
|
||||
way. If you use functional-style, the first argument will end up on the stack top.
|
||||
|
||||
|
||||
Access to External Variables and Functions
|
||||
------------------------------------------
|
||||
|
||||
Solidity variables and other identifiers can be accessed by simply using their name.
|
||||
For storage and memory variables, this will push the address and not the value onto the
|
||||
stack. Also note that non-struct and non-array storage variable addresses occupy two slots
|
||||
on the stack: One for the address and one for the byte offset inside the storage slot.
|
||||
In assignments (see below), we can even use local Solidity variables to assign to.
|
||||
|
||||
Functions external to inline assembly can also be accessed: The assembly will
|
||||
push their entry label (with virtual function resolution applied). The calling semantics
|
||||
in solidity are:
|
||||
|
||||
- the caller pushes return label, arg1, arg2, ..., argn
|
||||
- the call returns with ret1, ret2, ..., retn
|
||||
|
||||
This feature is still a bit cumbersome to use, because the stack offset essentially
|
||||
changes during the call, and thus references to local variables will be wrong.
|
||||
It is planned that the stack height changes can be specified in inline assembly.
|
||||
|
||||
.. code::
|
||||
|
||||
pragma solidity ^0.4.0;
|
||||
|
||||
contract C {
|
||||
uint b;
|
||||
function f(uint x) returns (uint r) {
|
||||
assembly {
|
||||
b pop // remove the offset, we know it is zero
|
||||
sload
|
||||
x
|
||||
mul
|
||||
=: r // assign to return variable r
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Labels
|
||||
------
|
||||
|
||||
Another problem in EVM assembly is that ``jump`` and ``jumpi`` use absolute addresses
|
||||
which can change easily. Solidity inline assembly provides labels to make the use of
|
||||
jumps easier. The following code computes an element in the Fibonacci series.
|
||||
|
||||
.. code::
|
||||
|
||||
{
|
||||
let n := calldataload(4)
|
||||
let a := 1
|
||||
let b := a
|
||||
loop:
|
||||
jumpi(loopend, eq(n, 0))
|
||||
a add swap1
|
||||
n := sub(n, 1)
|
||||
jump(loop)
|
||||
loopend:
|
||||
mstore(0, a)
|
||||
return(0, 0x20)
|
||||
}
|
||||
|
||||
Please note that automatically accessing stack variables can only work if the
|
||||
assembler knows the current stack height. This fails to work if the jump source
|
||||
and target have different stack heights. It is still fine to use such jumps,
|
||||
you should just not access any stack variables (even assembly variables) in that case.
|
||||
|
||||
Furthermore, the stack height analyser goes through the code opcode by opcode
|
||||
(and not according to control flow), so in the following case, the assembler
|
||||
will have a wrong impression about the stack height at label ``two``:
|
||||
|
||||
.. code::
|
||||
|
||||
{
|
||||
jump(two)
|
||||
one:
|
||||
// Here the stack height is 1 (because we pushed 7),
|
||||
// but the assembler thinks it is 0 because it reads
|
||||
// from top to bottom.
|
||||
// Accessing stack variables here will lead to errors.
|
||||
jump(three)
|
||||
two:
|
||||
7 // push something onto the stack
|
||||
jump(one)
|
||||
three:
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
``invalidJumpLabel`` is a pre-defined label. Jumping to this location will always
|
||||
result in an invalid jump, effectively aborting execution of the code.
|
||||
|
||||
Declaring Assembly-Local Variables
|
||||
----------------------------------
|
||||
|
||||
You can use the ``let`` keyword to declare variables that are only visible in
|
||||
inline assembly and actually only in the current ``{...}``-block. What happens
|
||||
is that the ``let`` instruction will create a new stack slot that is reserved
|
||||
for the variable and automatically removed again when the end of the block
|
||||
is reached. You need to provide an initial value for the variable which can
|
||||
be just ``0``, but it can also be a complex functional-style expression.
|
||||
|
||||
.. code::
|
||||
|
||||
pragma solidity ^0.4.0;
|
||||
|
||||
contract C {
|
||||
function f(uint x) returns (uint b) {
|
||||
assembly {
|
||||
let v := add(x, 1)
|
||||
mstore(0x80, v)
|
||||
{
|
||||
let y := add(sload(v), 1)
|
||||
b := y
|
||||
} // y is "deallocated" here
|
||||
b := add(b, v)
|
||||
} // v is "deallocated" here
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Assignments
|
||||
-----------
|
||||
|
||||
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.
|
||||
|
||||
There are two kinds of assignments: Functional-style and instruction-style.
|
||||
For functional-style assignments (``variable := value``), you need to provide a value in a
|
||||
functional-style expression that results in exactly one stack value
|
||||
and for instruction-style (``=: variable``), the value is just taken from the stack top.
|
||||
For both ways, the colon points to the name of the variable.
|
||||
|
||||
.. code::
|
||||
|
||||
assembly {
|
||||
let v := 0 // functional-style assignment as part of variable declaration
|
||||
let g := add(v, 2)
|
||||
sload(10)
|
||||
=: v // instruction style assignment, puts the result of sload(10) into v
|
||||
}
|
||||
|
||||
|
||||
Things to Avoid
|
||||
---------------
|
||||
|
||||
Inline assembly might have a quite high-level look, but it actually is extremely
|
||||
low-level. The only thing the assembler does for you is re-arranging
|
||||
functional-style opcodes, managing jump labels, counting stack height for
|
||||
variable access and removing stack slots for assembly-local variables when the end
|
||||
of their block is reached. Especially for those two last cases, it is important
|
||||
to know that the assembler only counts stack height from top to bottom, not
|
||||
necessarily following control flow. Furthermore, operations like swap will only
|
||||
swap the contents of the stack but not the location of variables.
|
||||
|
||||
Conventions in Solidity
|
||||
-----------------------
|
||||
|
||||
In contrast to EVM assembly, Solidity knows types which are narrower than 256 bits,
|
||||
e.g. ``uint24``. In order to make them more efficient, most arithmetic operations just
|
||||
treat them as 256-bit numbers and the higher-order bits are only cleaned at the
|
||||
point where it is necessary, i.e. just shortly before they are written to memory
|
||||
or before comparisons are performed. This means that if you access such a variable
|
||||
from within inline assembly, you might have to manually clean the higher order bits
|
||||
first.
|
||||
|
||||
Solidity manages memory in a very simple way: There is a "free memory pointer"
|
||||
at position ``0x40`` in memory. If you want to allocate memory, just use the memory
|
||||
from that point on and update the pointer accordingly.
|
||||
|
||||
Elements in memory arrays in Solidity always occupy multiples of 32 bytes (yes, this is
|
||||
even true for ``byte[]``, but not for ``bytes`` and ``string``). Multi-dimensional memory
|
||||
arrays are pointers to memory arrays. The length of a dynamic array is stored at the
|
||||
first slot of the array and then only the array elements follow.
|
||||
|
||||
.. warning::
|
||||
Statically-sized memory arrays do not have a length field, but it will be added soon
|
||||
to allow better convertibility between statically- and dynamically-sized arrays, so
|
||||
please do not rely on that.
|
||||
#. If you access an array at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``).
|
||||
#. If you access a fixed-length ``bytesN`` at a too large or negative index.
|
||||
#. If you call a function via a message call but it does not finish properly (i.e. it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall`` or ``callcode`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``.
|
||||
#. If you create a contract using the ``new`` keyword but the contract creation does not finish properly (see above for the definition of "not finish properly").
|
||||
#. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``).
|
||||
#. If you shift by a negative amount.
|
||||
#. If you convert a value too big or negative into an enum type.
|
||||
#. If you perform an external function call targeting a contract that contains no code.
|
||||
#. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function).
|
||||
#. If your contract receives Ether via a public getter function.
|
||||
#. If you call a zero-initialized variable of internal function type.
|
||||
#. If a ``.transfer()`` fails.
|
||||
#. If you call ``assert`` with an argument that evaluates to false.
|
||||
|
||||
While a user-provided exception is generated in the following situations:
|
||||
|
||||
#. Calling ``throw``.
|
||||
#. Calling ``require`` with an argument that evaluates to ``false``.
|
||||
|
||||
Internally, Solidity performs a revert operation (instruction ``0xfd``) when a user-provided exception is thrown or the condition of
|
||||
a ``require`` call is not met. In contrast, it performs an invalid operation
|
||||
(instruction ``0xfe``) if a runtime exception is encountered or the condition of an ``assert`` call is not met. In both cases, this causes
|
||||
the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect
|
||||
did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction
|
||||
(or at least call) without effect.
|
||||
|
||||
If contracts are written so that ``assert`` is only used to test internal conditions and ``require``
|
||||
is used in case of malformed input, a formal analysis tool that verifies that the invalid
|
||||
opcode can never be reached can be used to check for the absence of errors assuming valid inputs.
|
||||
|
||||
@ -68,7 +68,7 @@ creator. Save it. Then ``selfdestruct(creator);`` to kill and return funds.
|
||||
|
||||
Note that if you ``import "mortal"`` at the top of your contracts and declare
|
||||
``contract SomeContract is mortal { ...`` and compile with a compiler that already
|
||||
has it (which includes `browser-solidity <https://ethereum.github.io/browser-solidity/>`_), then
|
||||
has it (which includes `Remix <https://remix.ethereum.org/>`_), then
|
||||
``kill()`` is taken care of for you. Once a contract is "mortal", then you can
|
||||
``contractname.kill.sendTransaction({from:eth.coinbase})``, just the same as my
|
||||
examples.
|
||||
@ -641,7 +641,7 @@ Not yet, as this requires two levels of dynamic arrays (``string`` is a dynamic
|
||||
If you issue a call for an array, it is possible to retrieve the whole array? Or must you write a helper function for that?
|
||||
===========================================================================================================================
|
||||
|
||||
The automatic accessor function for a public state variable of array type only returns
|
||||
The automatic getter function for a public state variable of array type only returns
|
||||
individual elements. If you want to return the complete array, you have to
|
||||
manually write a function to do that.
|
||||
|
||||
@ -660,16 +660,6 @@ https://github.com/ethereum/wiki/wiki/Subtleties
|
||||
After a successful CREATE operation's sub-execution, if the operation returns x, 5 * len(x) gas is subtracted from the remaining gas before the contract is created. If the remaining gas is less than 5 * len(x), then no gas is subtracted, the code of the created contract becomes the empty string, but this is not treated as an exceptional condition - no reverts happen.
|
||||
|
||||
|
||||
How do I use ``.send()``?
|
||||
=========================
|
||||
|
||||
If you want to send 20 Ether from a contract to the address ``x``, you use ``x.send(20 ether);``.
|
||||
Here, ``x`` can be a plain address or a contract. If the contract already explicitly defines
|
||||
a function ``send`` (and thus overwrites the special function), you can use ``address(x).send(20 ether);``.
|
||||
|
||||
Note that the call to ``send`` may fail in certain conditions, such as if you have insufficient funds, so you should always check the return value.
|
||||
``send`` returns ``true`` if the send was successful and ``false`` otherwise.
|
||||
|
||||
What does the following strange check do in the Custom Token contract?
|
||||
======================================================================
|
||||
|
||||
|
||||
@ -35,10 +35,10 @@ TypeNameList = '(' ( TypeName (',' TypeName )* )? ')'
|
||||
|
||||
// semantic restriction: mappings and structs (recursively) containing mappings
|
||||
// are not allowed in argument lists
|
||||
VariableDeclaration = TypeName Identifier
|
||||
VariableDeclaration = TypeName StorageLocation? Identifier
|
||||
|
||||
TypeName = ElementaryTypeName
|
||||
| UserDefinedTypeName StorageLocation?
|
||||
| UserDefinedTypeName
|
||||
| Mapping
|
||||
| ArrayTypeName
|
||||
| FunctionTypeName
|
||||
@ -46,7 +46,7 @@ TypeName = ElementaryTypeName
|
||||
UserDefinedTypeName = Identifier ( '.' Identifier )*
|
||||
|
||||
Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')'
|
||||
ArrayTypeName = TypeName '[' Expression? ']' StorageLocation?
|
||||
ArrayTypeName = TypeName '[' Expression? ']'
|
||||
FunctionTypeName = 'function' TypeNameList ( 'internal' | 'external' | 'constant' | 'payable' )*
|
||||
( 'returns' TypeNameList )?
|
||||
StorageLocation = 'memory' | 'storage'
|
||||
@ -68,7 +68,8 @@ Continue = 'continue'
|
||||
Break = 'break'
|
||||
Return = 'return' Expression?
|
||||
Throw = 'throw'
|
||||
VariableDefinition = VariableDeclaration ( '=' Expression )?
|
||||
VariableDefinition = ('var' IdentifierList | VariableDeclaration) ( '=' Expression )?
|
||||
IdentifierList = '(' ( Identifier? ',' )* Identifier? ')'
|
||||
|
||||
// Precedence by order (see github.com/ethereum/solidity/pull/732)
|
||||
Expression =
|
||||
@ -97,8 +98,14 @@ PrimaryExpression = Identifier
|
||||
| StringLiteral
|
||||
| ElementaryTypeNameExpression
|
||||
|
||||
FunctionCall = ( PrimaryExpression | NewExpression | TypeName ) ( ( '.' Identifier ) | ( '[' Expression ']' ) )* '(' Expression? ( ',' Expression )* ')'
|
||||
NewExpression = 'new' Identifier
|
||||
ExpressionList = Expression ( ',' Expression )*
|
||||
NameValueList = Identifier ':' Expression ( ',' Identifier ':' Expression )*
|
||||
|
||||
FunctionCall = ( PrimaryExpression | NewExpression | TypeName ) ( ( '.' Identifier ) | ( '[' Expression ']' ) )* '(' FunctionCallArguments ')'
|
||||
FunctionCallArguments = '{' NameValueList? '}'
|
||||
| ExpressionList?
|
||||
|
||||
NewExpression = 'new' TypeName
|
||||
MemberAccess = Expression '.' Identifier
|
||||
IndexAccess = Expression '[' Expression? ']'
|
||||
|
||||
@ -108,7 +115,7 @@ NumberUnit = 'wei' | 'szabo' | 'finney' | 'ether'
|
||||
| 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years'
|
||||
HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
|
||||
StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
|
||||
Identifier = [a-zA-Z_] [a-zA-Z_0-9]*
|
||||
Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]*
|
||||
|
||||
HexNumber = '0x' [0-9a-fA-F]+
|
||||
DecimalNumber = [0-9]+
|
||||
|
||||
@ -2,7 +2,7 @@ Solidity
|
||||
========
|
||||
|
||||
Solidity is a contract-oriented, high-level language whose syntax is similar to that of JavaScript
|
||||
and it is designed to target the Ethereum Virtual Machine.
|
||||
and it is designed to target the Ethereum Virtual Machine (EVM).
|
||||
|
||||
Solidity is statically typed, supports inheritance, libraries and complex
|
||||
user-defined types among other features.
|
||||
@ -11,8 +11,8 @@ As you will see, it is possible to create contracts for voting,
|
||||
crowdfunding, blind auctions, multi-signature wallets and more.
|
||||
|
||||
.. note::
|
||||
The best way to try out Solidity right now is using the
|
||||
`Browser-Based Compiler <https://ethereum.github.io/browser-solidity/>`_
|
||||
The best way to try out Solidity right now is using
|
||||
`Remix <https://remix.ethereum.org/>`_
|
||||
(it can take a while to load, please be patient).
|
||||
|
||||
Useful links
|
||||
@ -33,12 +33,15 @@ Useful links
|
||||
Available Solidity Integrations
|
||||
-------------------------------
|
||||
|
||||
* `Browser-Based Compiler <https://ethereum.github.io/browser-solidity/>`_
|
||||
* `Remix <https://remix.ethereum.org/>`_
|
||||
Browser-based IDE with integrated compiler and Solidity runtime environment without server-side components.
|
||||
|
||||
* `Ethereum Studio <https://live.ether.camp/>`_
|
||||
Specialized web IDE that also provides shell access to a complete Ethereum environment.
|
||||
|
||||
* `IntelliJ IDEA plugin <https://plugins.jetbrains.com/plugin/9475-intellij-solidity>`_
|
||||
Solidity plugin for IntelliJ IDEA (and all other JetBrains IDEs)
|
||||
|
||||
* `Visual Studio Extension <https://visualstudiogallery.msdn.microsoft.com/96221853-33c4-4531-bdd5-d2ea5acc4799/>`_
|
||||
Solidity plugin for Microsoft Visual Studio that includes the Solidity compiler.
|
||||
|
||||
@ -106,7 +109,7 @@ and the :ref:`Ethereum Virtual Machine <the-ethereum-virtual-machine>`.
|
||||
The next section will explain several *features* of Solidity by giving
|
||||
useful :ref:`example contracts <voting>`
|
||||
Remember that you can always try out the contracts
|
||||
`in your browser <https://ethereum.github.io/browser-solidity>`_!
|
||||
`in your browser <https://remix.ethereum.org>`_!
|
||||
|
||||
The last and most extensive section will cover all aspects of Solidity in depth.
|
||||
|
||||
@ -130,6 +133,7 @@ Contents
|
||||
solidity-by-example.rst
|
||||
solidity-in-depth.rst
|
||||
security-considerations.rst
|
||||
using-the-compiler.rst
|
||||
style-guide.rst
|
||||
common-patterns.rst
|
||||
contributing.rst
|
||||
|
||||
@ -15,34 +15,35 @@ are not guaranteed to be working and despite best efforts they might contain und
|
||||
and/or broken changes. We recommend using the latest release. Package installers below
|
||||
will use the latest release.
|
||||
|
||||
Browser-Solidity
|
||||
================
|
||||
Remix
|
||||
=====
|
||||
|
||||
If you just want to try Solidity for small contracts, you
|
||||
can try `browser-solidity <https://ethereum.github.io/browser-solidity>`_
|
||||
can try `Remix <https://remix.ethereum.org/>`_
|
||||
which does not need any installation. If you want to use it
|
||||
without connection to the Internet, you can go to
|
||||
https://github.com/ethereum/browser-solidity/tree/gh-pages and
|
||||
download the .ZIP file as explained on that page.
|
||||
|
||||
|
||||
npm / Node.js
|
||||
=============
|
||||
|
||||
This is probably the most portable and most convenient way to install Solidity locally.
|
||||
|
||||
A platform-independent JavaScript library is provided by compiling the C++ source
|
||||
into JavaScript using Emscripten for browser-solidity and there is also an npm
|
||||
package available.
|
||||
into JavaScript using Emscripten. It can be used in projects directly (such as Remix).
|
||||
Please refer to the `solc-js <https://github.com/ethereum/solc-js>`_ repository for instructions.
|
||||
|
||||
To install it, simply use
|
||||
It also contains a commandline tool called `solcjs`, which can be installed via npm:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
npm install solc
|
||||
npm install -g solc
|
||||
|
||||
Details about the usage of the Node.js package can be found in the
|
||||
`solc-js repository <https://github.com/ethereum/solc-js>`_.
|
||||
.. note::
|
||||
|
||||
The comandline options of `solcjs` are not compatible with `solc` and tools (such as `geth`)
|
||||
expecting the behaviour of `solc` will not work with `solcjs`.
|
||||
|
||||
Docker
|
||||
======
|
||||
@ -82,6 +83,12 @@ If you want to use the cutting edge developer version:
|
||||
sudo apt-get update
|
||||
sudo apt-get install solc
|
||||
|
||||
Arch Linux also has packages, albeit limited to the latest development version:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
pacman -S solidity-git
|
||||
|
||||
Homebrew is missing pre-built bottles at the time of writing,
|
||||
following a Jenkins to TravisCI migration, but Homebrew
|
||||
should still work just fine as a means to build-from-source.
|
||||
@ -95,6 +102,22 @@ We will re-add the pre-built bottles soon.
|
||||
brew install solidity
|
||||
brew linkapps solidity
|
||||
|
||||
If you need a specific version of Solidity you can install a
|
||||
Homebrew formula directly from Github.
|
||||
|
||||
View
|
||||
`solidity.rb commits on Github <https://github.com/ethereum/homebrew-ethereum/commits/master/solidity.rb>`_.
|
||||
|
||||
Follow the history links until you have a raw file link of a
|
||||
specific commit of ``solidity.rb``.
|
||||
|
||||
Install it using ``brew``:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
brew unlink solidity
|
||||
# Install 0.4.8
|
||||
brew install https://raw.githubusercontent.com/ethereum/homebrew-ethereum/77cce03da9f289e5a3ffe579840d3c5dc0a62717/solidity.rb
|
||||
|
||||
.. _building-from-source:
|
||||
|
||||
@ -119,6 +142,11 @@ you should fork Solidity and add your personal fork as a second remote:
|
||||
cd solidity
|
||||
git remote add personal git@github.com:[username]/solidity.git
|
||||
|
||||
Solidity has git submodules. Ensure they are properly loaded:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
git submodule update --init --recursive
|
||||
|
||||
Prerequisites - macOS
|
||||
---------------------
|
||||
@ -192,7 +220,14 @@ Building Solidity is quite similar on Linux, macOS and other Unices:
|
||||
cd build
|
||||
cmake .. && make
|
||||
|
||||
And even on Windows:
|
||||
or even easier:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
#note: this will install binaries solc and soltest at usr/local/bin
|
||||
./scripts/build.sh
|
||||
|
||||
And even for Windows:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
@ -211,6 +246,25 @@ Alternatively, you can build for Windows on the command-line, like so:
|
||||
|
||||
cmake --build . --config RelWithDebInfo
|
||||
|
||||
The version string in detail
|
||||
============================
|
||||
|
||||
The Solidity version string contains four parts:
|
||||
|
||||
- the version number
|
||||
- pre-release tag, usually set to ``develop.YYYY.MM.DD`` or ``nightly.YYYY.MM.DD``
|
||||
- commit in the format of ``commit.GITHASH``
|
||||
- platform has arbitrary number of items, containing details about the platform and compiler
|
||||
|
||||
If there are local modifications, the commit will be postfixed with ``.mod``.
|
||||
|
||||
These parts are combined as required by Semver, where the Solidity pre-release tag equals to the Semver pre-release
|
||||
and the Solidity commit and platform combined make up the Semver build metadata.
|
||||
|
||||
A relase example: ``0.4.8+commit.60cc1668.Emscripten.clang``.
|
||||
|
||||
A pre-release example: ``0.4.9-nightly.2017.1.17+commit.6ecb4aa3.Emscripten.clang``
|
||||
|
||||
Important information about versioning
|
||||
======================================
|
||||
|
||||
@ -227,4 +281,4 @@ Example:
|
||||
3. a breaking change is introduced - version is bumped to 0.5.0
|
||||
4. the 0.5.0 release is made
|
||||
|
||||
This behaviour works well with the version pragma.
|
||||
This behaviour works well with the :ref:`version pragma <version_pragma>`.
|
||||
|
||||
@ -109,8 +109,7 @@ that does not allow any arithmetic operations. It is suitable for
|
||||
storing addresses of contracts or keypairs belonging to external
|
||||
persons. The keyword ``public`` automatically generates a function that
|
||||
allows you to access the current value of the state variable.
|
||||
Without this keyword, other contracts have no way to access the variable
|
||||
and only the code of this contract can write to it.
|
||||
Without this keyword, other contracts have no way to access the variable.
|
||||
The function will look something like this::
|
||||
|
||||
function minter() returns (address) { return minter; }
|
||||
@ -132,7 +131,7 @@ too far, though, as it is neither possible to obtain a list of all keys of
|
||||
a mapping, nor a list of all values. So either keep in mind (or
|
||||
better, keep a list or use a more advanced data type) what you
|
||||
added to the mapping or use it in a context where this is not needed,
|
||||
like this one. The accessor function created by the ``public`` keyword
|
||||
like this one. The getter function created by the ``public`` keyword
|
||||
is a bit more complex in this case. It roughly looks like the
|
||||
following::
|
||||
|
||||
@ -283,8 +282,8 @@ determined at the time the contract is created
|
||||
(it is derived from the creator address and the number
|
||||
of transactions sent from that address, the so-called "nonce").
|
||||
|
||||
Apart from the fact whether an account stores code or not,
|
||||
the EVM treats the two types equally, though.
|
||||
Regardless of whether or not the account stores code, the two types are
|
||||
treated equally by the EVM.
|
||||
|
||||
Every account has a persistent key-value store mapping 256-bit words to 256-bit
|
||||
words called **storage**.
|
||||
|
||||
@ -7,6 +7,8 @@ and pragma directives.
|
||||
|
||||
.. index:: ! pragma, version
|
||||
|
||||
.. _version_pragma:
|
||||
|
||||
Version Pragma
|
||||
==============
|
||||
|
||||
@ -79,8 +81,9 @@ Paths
|
||||
-----
|
||||
|
||||
In the above, ``filename`` is always treated as a path with ``/`` as directory separator,
|
||||
``.`` as the current and ``..`` as the parent directory. Path names that do not start
|
||||
with ``.`` are treated as absolute paths.
|
||||
``.`` as the current and ``..`` as the parent directory. When ``.`` or ``..`` is followed by a character except ``/``,
|
||||
it is not considered as the current or the parent directory.
|
||||
All path names are treated as absolute paths unless they start with the current ``.`` or the parent directory ``..``.
|
||||
|
||||
To import a file ``x`` from the same directory as the current file, use ``import "./x" as x;``.
|
||||
If you use ``import "x" as x;`` instead, a different file could be referenced
|
||||
@ -150,9 +153,9 @@ remapping ``=/``.
|
||||
If there are multiple remappings that lead to a valid file, the remapping
|
||||
with the longest common prefix is chosen.
|
||||
|
||||
**browser-solidity**:
|
||||
**Remix**:
|
||||
|
||||
The `browser-based compiler <https://ethereum.github.io/browser-solidity>`_
|
||||
`Remix <https://remix.ethereum.org/>`_
|
||||
provides an automatic remapping for github and will also automatically retrieve
|
||||
the file over the network:
|
||||
You can import the iterable mapping by e.g.
|
||||
|
||||
@ -137,31 +137,6 @@ Different types have different rules for cleaning up invalid values:
|
||||
| | |will be thrown |
|
||||
+---------------+---------------+-------------------+
|
||||
|
||||
|
||||
*****************
|
||||
Esoteric Features
|
||||
*****************
|
||||
|
||||
There are some types in Solidity's type system that have no counterpart in the syntax. One of these types are the types of functions. But still, using ``var`` it is possible to have local variables of these types::
|
||||
|
||||
contract FunctionSelector {
|
||||
function select(bool useB, uint x) returns (uint z) {
|
||||
var f = a;
|
||||
if (useB) f = b;
|
||||
return f(x);
|
||||
}
|
||||
|
||||
function a(uint x) returns (uint z) {
|
||||
return x * x;
|
||||
}
|
||||
|
||||
function b(uint x) returns (uint z) {
|
||||
return 2 * x;
|
||||
}
|
||||
}
|
||||
|
||||
Calling ``select(false, x)`` will compute ``x * x`` and ``select(true, x)`` will compute ``2 * x``.
|
||||
|
||||
.. index:: optimizer, common subexpression elimination, constant propagation
|
||||
|
||||
*************************
|
||||
@ -246,45 +221,6 @@ This means the following source mappings represent the same information:
|
||||
|
||||
``1:2:1;:9;2::2;;``
|
||||
|
||||
|
||||
.. index:: ! commandline compiler, compiler;commandline, ! solc, ! linker
|
||||
|
||||
.. _commandline-compiler:
|
||||
|
||||
******************************
|
||||
Using the Commandline Compiler
|
||||
******************************
|
||||
|
||||
One of the build targets of the Solidity repository is ``solc``, the solidity commandline compiler.
|
||||
Using ``solc --help`` provides you with an explanation of all options. The compiler can produce various outputs, ranging from simple binaries and assembly over an abstract syntax tree (parse tree) to estimations of gas usage.
|
||||
If you only want to compile a single file, you run it as ``solc --bin sourceFile.sol`` and it will print the binary. Before you deploy your contract, activate the optimizer while compiling using ``solc --optimize --bin sourceFile.sol``. If you want to get some of the more advanced output variants of ``solc``, it is probably better to tell it to output everything to separate files using ``solc -o outputDirectory --bin --ast --asm sourceFile.sol``.
|
||||
|
||||
The commandline compiler will automatically read imported files from the filesystem, but
|
||||
it is also possible to provide path redirects using ``context:prefix=path`` in the following way:
|
||||
|
||||
::
|
||||
|
||||
solc github.com/ethereum/dapp-bin/=/usr/local/lib/dapp-bin/ =/usr/local/lib/fallback file.sol
|
||||
|
||||
This essentially instructs the compiler to search for anything starting with
|
||||
``github.com/ethereum/dapp-bin/`` under ``/usr/local/lib/dapp-bin`` and if it does not
|
||||
find the file there, it will look at ``/usr/local/lib/fallback`` (the empty prefix
|
||||
always matches). ``solc`` will not read files from the filesystem that lie outside of
|
||||
the remapping targets and outside of the directories where explicitly specified source
|
||||
files reside, so things like ``import "/etc/passwd";`` only work if you add ``=/`` as a remapping.
|
||||
|
||||
You can restrict remappings to only certain source files by prefixing a context.
|
||||
|
||||
The section on :ref:`import` provides more details on remappings.
|
||||
|
||||
If there are multiple matches due to remappings, the one with the longest common prefix is selected.
|
||||
|
||||
If your contracts use :ref:`libraries <libraries>`, you will notice that the bytecode contains substrings of the form ``__LibraryName______``. You can use ``solc`` as a linker meaning that it will insert the library addresses for you at those points:
|
||||
|
||||
Either add ``--libraries "Math:0x12345678901234567890 Heap:0xabcdef0123456"`` to your command to provide an address for each library or store the string in a file (one library per line) and run ``solc`` using ``--libraries fileName``.
|
||||
|
||||
If ``solc`` is called with the option ``--link``, all input files are interpreted to be unlinked binaries (hex-encoded) in the ``__LibraryName____``-format given above and are linked in-place (if the input is read from stdin, it is written to stdout). All options except ``--libraries`` are ignored (including ``-o``) in this case.
|
||||
|
||||
*****************
|
||||
Contract Metadata
|
||||
*****************
|
||||
@ -427,7 +363,7 @@ Tips and Tricks
|
||||
|
||||
* Use ``delete`` on arrays to delete all its elements.
|
||||
* Use shorter types for struct elements and sort them such that short types are grouped together. This can lower the gas costs as multiple SSTORE operations might be combined into a single (SSTORE costs 5000 or 20000 gas, so this is what you want to optimise). Use the gas price estimator (with optimiser enabled) to check!
|
||||
* Make your state variables public - the compiler will create :ref:`getters <visibility-and-accessors>` for you for free.
|
||||
* Make your state variables public - the compiler will create :ref:`getters <visibility-and-getters>` for you for free.
|
||||
* If you end up checking conditions on input or state a lot at the beginning of your functions, try using :ref:`modifiers`.
|
||||
* If your contract has a function called ``send`` but you want to use the built-in send-function, use ``address(contractVariable).send(amount)``.
|
||||
* Initialise storage structs with a single assignment: ``x = MyStruct({a: 1, b: 2});``
|
||||
@ -499,7 +435,7 @@ The following is the order of precedence for operators, listed in order of evalu
|
||||
| *16* | Comma operator | ``,`` |
|
||||
+------------+-------------------------------------+--------------------------------------------+
|
||||
|
||||
.. index:: block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send
|
||||
.. index:: assert, block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, revert, require, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send
|
||||
|
||||
Global Variables
|
||||
================
|
||||
@ -517,6 +453,9 @@ Global Variables
|
||||
- ``now`` (``uint``): current block timestamp (alias for ``block.timestamp``)
|
||||
- ``tx.gasprice`` (``uint``): gas price of the transaction
|
||||
- ``tx.origin`` (``address``): sender of the transaction (full call chain)
|
||||
- ``assert(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for internal error)
|
||||
- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for malformed input)
|
||||
- ``revert()``: abort execution and revert state changes
|
||||
- ``keccak256(...) returns (bytes32)``: compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments
|
||||
- ``sha3(...) returns (bytes32)``: an alias to `keccak256()`
|
||||
- ``sha256(...) returns (bytes32)``: compute the SHA-256 hash of the (tightly packed) arguments
|
||||
@ -527,8 +466,9 @@ Global Variables
|
||||
- ``this`` (current contract's type): the current contract, explicitly convertible to ``address``
|
||||
- ``super``: the contract one level higher in the inheritance hierarchy
|
||||
- ``selfdestruct(address recipient)``: destroy the current contract, sending its funds to the given address
|
||||
- ``<address>.balance`` (``uint256``): balance of the address in Wei
|
||||
- ``<address>.send(uint256 amount) returns (bool)``: send given amount of Wei to address, returns ``false`` on failure
|
||||
- ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei
|
||||
- ``<address>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure
|
||||
- ``<address>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure
|
||||
|
||||
.. index:: visibility, public, private, external, internal
|
||||
|
||||
@ -541,7 +481,7 @@ Function Visibility Specifiers
|
||||
return true;
|
||||
}
|
||||
|
||||
- ``public``: visible externally and internally (creates accessor function for storage/state variables)
|
||||
- ``public``: visible externally and internally (creates getter function for storage/state variables)
|
||||
- ``private``: only visible in the current contract
|
||||
- ``external``: only visible externally (only for functions) - i.e. can only be message-called (via ``this.func``)
|
||||
- ``internal``: only visible internally
|
||||
|
||||
@ -117,7 +117,7 @@ Sending and Receiving Ether
|
||||
During the execution of the fallback function, the contract can only rely
|
||||
on the "gas stipend" (2300 gas) being available to it at that time. This stipend is not enough to access storage in any way.
|
||||
To be sure that your contract can receive Ether in that way, check the gas requirements of the fallback function
|
||||
(for example in the "details" section in browser-solidity).
|
||||
(for example in the "details" section in Remix).
|
||||
|
||||
- There is a way to forward more gas to the receiving contract using
|
||||
``addr.call.value(x)()``. This is essentially the same as ``addr.send(x)``,
|
||||
|
||||
@ -106,6 +106,10 @@ of votes.
|
||||
if (sender.voted)
|
||||
throw;
|
||||
|
||||
// Self-delegation is not allowed.
|
||||
if (to == msg.sender)
|
||||
throw;
|
||||
|
||||
// Forward the delegation as long as
|
||||
// `to` also delegated.
|
||||
// In general, such loops are very dangerous,
|
||||
@ -114,16 +118,12 @@ of votes.
|
||||
// In this case, the delegation will not be executed,
|
||||
// but in other situations, such loops might
|
||||
// cause a contract to get "stuck" completely.
|
||||
while (
|
||||
voters[to].delegate != address(0) &&
|
||||
voters[to].delegate != msg.sender
|
||||
) {
|
||||
while (voters[to].delegate != address(0)) {
|
||||
to = voters[to].delegate;
|
||||
}
|
||||
|
||||
// We found a loop in the delegation, not allowed.
|
||||
if (to == msg.sender) {
|
||||
throw;
|
||||
// We found a loop in the delegation, not allowed.
|
||||
if (to == msg.sender)
|
||||
throw;
|
||||
}
|
||||
|
||||
// Since `sender` is a reference, this
|
||||
|
||||
@ -16,4 +16,5 @@ If something is missing here, please contact us on
|
||||
units-and-global-variables.rst
|
||||
control-structures.rst
|
||||
contracts.rst
|
||||
assembly.rst
|
||||
miscellaneous.rst
|
||||
|
||||
@ -28,7 +28,7 @@ State variables are values which are permanently stored in contract storage.
|
||||
}
|
||||
|
||||
See the :ref:`types` section for valid state variable types and
|
||||
:ref:`visibility-and-accessors` for possible choices for
|
||||
:ref:`visibility-and-getters` for possible choices for
|
||||
visibility.
|
||||
|
||||
.. _structure-functions:
|
||||
@ -49,7 +49,7 @@ Functions are the executable units of code within a contract.
|
||||
}
|
||||
|
||||
:ref:`function-calls` can happen internally or externally
|
||||
and have different levels of visibility (:ref:`visibility-and-accessors`)
|
||||
and have different levels of visibility (:ref:`visibility-and-getters`)
|
||||
towards other contracts.
|
||||
|
||||
.. _structure-function-modifiers:
|
||||
|
||||
@ -164,7 +164,7 @@ Functions should be grouped according to their visibility and ordered:
|
||||
- internal
|
||||
- private
|
||||
|
||||
Within a grouping, place the `constant` functions last.
|
||||
Within a grouping, place the ``constant`` functions last.
|
||||
|
||||
Yes::
|
||||
|
||||
|
||||
@ -64,7 +64,7 @@ expression ``x << y`` is equivalent to ``x * 2**y`` and ``x >> y`` is
|
||||
equivalent to ``x / 2**y``. This means that shifting negative numbers
|
||||
sign extends. Shifting by a negative amount throws a runtime exception.
|
||||
|
||||
.. index:: address, balance, send, call, callcode, delegatecall
|
||||
.. index:: address, balance, send, call, callcode, delegatecall, transfer
|
||||
|
||||
.. _address:
|
||||
|
||||
@ -80,27 +80,31 @@ Operators:
|
||||
Members of Addresses
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
* ``balance`` and ``send``
|
||||
* ``balance`` and ``transfer``
|
||||
|
||||
For a quick reference, see :ref:`address_related`.
|
||||
|
||||
It is possible to query the balance of an address using the property ``balance``
|
||||
and to send Ether (in units of wei) to an address using the ``send`` function:
|
||||
and to send Ether (in units of wei) to an address using the ``transfer`` function:
|
||||
|
||||
::
|
||||
|
||||
address x = 0x123;
|
||||
address myAddress = this;
|
||||
if (x.balance < 10 && myAddress.balance >= 10) x.send(10);
|
||||
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
|
||||
|
||||
.. note::
|
||||
If ``x`` is a contract address, its code (more specifically: its fallback function, if present) will be executed together with the ``send`` call (this is a limitation of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted. In this case, ``send`` returns ``false``.
|
||||
If ``x`` is a contract address, its code (more specifically: its fallback function, if present) will be executed together with the ``transfer`` call (this is a limitation of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted and the current contract will stop with an exception.
|
||||
|
||||
* ``send``
|
||||
|
||||
Send is the low-level counterpart of ``transfer``. If the execution fails, the current contract will not stop with an exception, but ``send`` will return ``false``.
|
||||
|
||||
.. warning::
|
||||
There are some dangers in using ``send``: The transfer fails if the call stack depth is at 1024
|
||||
(this can always be forced by the caller) and it also fails if the recipient runs out of gas. So in order
|
||||
to make safe Ether transfers, always check the return value of ``send`` or even better:
|
||||
Use a pattern where the recipient withdraws the money.
|
||||
to make safe Ether transfers, always check the return value of ``send``, use ``transfer`` or even better:
|
||||
use a pattern where the recipient withdraws the money.
|
||||
|
||||
* ``call``, ``callcode`` and ``delegatecall``
|
||||
|
||||
@ -119,6 +123,8 @@ In a similar way, the function ``delegatecall`` can be used: The difference is t
|
||||
|
||||
All three functions ``call``, ``delegatecall`` and ``callcode`` are very low-level functions and should only be used as a *last resort* as they break the type-safety of Solidity.
|
||||
|
||||
The ``.gas()`` option is available on all three methods, while the ``.value()`` option is not supported for ``delegatecall``.
|
||||
|
||||
.. note::
|
||||
All contracts inherit the members of address, so it is possible to query the balance of the
|
||||
current contract using ``this.balance``.
|
||||
@ -171,6 +177,19 @@ Fixed Point Numbers
|
||||
|
||||
**COMING SOON...**
|
||||
|
||||
.. index:: address, literal;address
|
||||
|
||||
.. _address_literals:
|
||||
|
||||
Address Literals
|
||||
----------------
|
||||
|
||||
Hexadecimal literals that pass the address checksum test, for example
|
||||
``0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF`` are of ``address`` type.
|
||||
Hexadecimal literals that are between 39 and 41 digits
|
||||
long and do not pass the checksum test produce
|
||||
a warning and are treated as regular rational number literals.
|
||||
|
||||
.. index:: literal, literal;rational
|
||||
|
||||
.. _rational_literals:
|
||||
@ -180,12 +199,14 @@ Rational and Integer Literals
|
||||
|
||||
Integer literals are formed from a sequence of numbers in the range 0-9.
|
||||
They are interpreted as decimals. For example, ``69`` means sixty nine.
|
||||
Octal literals do not exist in Solidity and leading zeros are ignored.
|
||||
For example, ``0100`` means one hundred.
|
||||
Octal literals do not exist in Solidity and leading zeros are invalid.
|
||||
|
||||
Decimal literals are formed by a ``.`` with at least one number on
|
||||
Decimal fraction literals are formed by a ``.`` with at least one number on
|
||||
one side. Examples include ``1.``, ``.1`` and ``1.3``.
|
||||
|
||||
Scientific notation is also supported, where the base can have fractions, while the exponent cannot.
|
||||
Examples include ``2e10``, ``-2e10``, ``2e-10``, ``2.5e1``.
|
||||
|
||||
Number literal expressions retain arbitrary precision until they are converted to a non-literal type (i.e. by
|
||||
using them together with a non-literal expression).
|
||||
This means that computations do not overflow and divisions do not truncate
|
||||
@ -213,7 +234,7 @@ a non-rational number).
|
||||
Integer literals and rational number literals belong to number literal types.
|
||||
Moreover, all number literal expressions (i.e. the expressions that
|
||||
contain only number literals and operators) belong to number literal
|
||||
types. So the number literal expressions `1 + 2` and `2 + 1` both
|
||||
types. So the number literal expressions ``1 + 2`` and ``2 + 1`` both
|
||||
belong to the same number literal type for the rational number three.
|
||||
|
||||
.. note::
|
||||
@ -242,7 +263,7 @@ a non-rational number).
|
||||
String Literals
|
||||
---------------
|
||||
|
||||
String literals are written with either double or single-quotes (``"foo"`` or ``'bar'``). They do not imply trailing zeroes as in C; `"foo"`` represents three bytes not four. As with integer literals, their type can vary, but they are implicitly convertible to ``bytes1``, ..., ``bytes32``, if they fit, to ``bytes`` and to ``string``.
|
||||
String literals are written with either double or single-quotes (``"foo"`` or ``'bar'``). They do not imply trailing zeroes as in C; ``"foo"`` represents three bytes not four. As with integer literals, their type can vary, but they are implicitly convertible to ``bytes1``, ..., ``bytes32``, if they fit, to ``bytes`` and to ``string``.
|
||||
|
||||
String literals support escape characters, such as ``\n``, ``\xNN`` and ``\uNNNN``. ``\xNN`` takes a hex value and inserts the appropriate byte, while ``\uNNNN`` takes a Unicode codepoint and inserts an UTF-8 sequence.
|
||||
|
||||
@ -530,8 +551,8 @@ So ``bytes`` should always be preferred over ``byte[]`` because it is cheaper.
|
||||
that you are accessing the low-level bytes of the UTF-8 representation,
|
||||
and not the individual characters!
|
||||
|
||||
It is possible to mark arrays ``public`` and have Solidity create an accessor.
|
||||
The numeric index will become a required parameter for the accessor.
|
||||
It is possible to mark arrays ``public`` and have Solidity create a getter.
|
||||
The numeric index will become a required parameter for the getter.
|
||||
|
||||
.. index:: ! array;allocating, new
|
||||
|
||||
@ -779,11 +800,11 @@ Because of this, mappings do not have a length or a concept of a key or value be
|
||||
Mappings are only allowed for state variables (or as storage reference types
|
||||
in internal functions).
|
||||
|
||||
It is possible to mark mappings ``public`` and have Solidity create an accessor.
|
||||
The ``_KeyType`` will become a required parameter for the accessor and it will
|
||||
It is possible to mark mappings ``public`` and have Solidity create a getter.
|
||||
The ``_KeyType`` will become a required parameter for the getter and it will
|
||||
return ``_ValueType``.
|
||||
|
||||
The ``_ValueType`` can be a mapping too. The accessor will have one parameter
|
||||
The ``_ValueType`` can be a mapping too. The getter will have one parameter
|
||||
for each ``_KeyType``, recursively.
|
||||
|
||||
::
|
||||
|
||||
@ -79,11 +79,13 @@ Block and Transaction Properties
|
||||
You can only access the hashes of the most recent 256 blocks, all other
|
||||
values will be zero.
|
||||
|
||||
.. index:: keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send
|
||||
.. index:: assert, revert, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send
|
||||
|
||||
Mathematical and Cryptographic Functions
|
||||
----------------------------------------
|
||||
|
||||
``assert(bool condition)``:
|
||||
throws if the condition is not met.
|
||||
``addmod(uint x, uint y, uint k) returns (uint)``:
|
||||
compute ``(x + y) % k`` where the addition is performed with arbitrary precision and does not wrap around at ``2**256``.
|
||||
``mulmod(uint x, uint y, uint k) returns (uint)``:
|
||||
@ -91,13 +93,15 @@ Mathematical and Cryptographic Functions
|
||||
``keccak256(...) returns (bytes32)``:
|
||||
compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments
|
||||
``sha3(...) returns (bytes32)``:
|
||||
alias to `keccak256()`
|
||||
alias to ``keccak256()``
|
||||
``sha256(...) returns (bytes32)``:
|
||||
compute the SHA-256 hash of the (tightly packed) arguments
|
||||
``ripemd160(...) returns (bytes20)``:
|
||||
compute RIPEMD-160 hash of the (tightly packed) arguments
|
||||
``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``:
|
||||
recover the address associated with the public key from elliptic curve signature or return zero on error
|
||||
``revert()``:
|
||||
abort execution and revert state changes
|
||||
|
||||
In the above, "tightly packed" means that the arguments are concatenated without padding.
|
||||
This means that the following are all identical::
|
||||
@ -124,15 +128,23 @@ Address Related
|
||||
|
||||
``<address>.balance`` (``uint256``):
|
||||
balance of the :ref:`address` in Wei
|
||||
``<address>.transfer(uint256 amount)``:
|
||||
send given amount of Wei to :ref:`address`, throws on failure
|
||||
``<address>.send(uint256 amount) returns (bool)``:
|
||||
send given amount of Wei to :ref:`address`, returns ``false`` on failure
|
||||
``<address>.call(...) returns (bool)``:
|
||||
issue low-level ``CALL``, returns ``false`` on failure
|
||||
``<address>.callcode(...) returns (bool)``:
|
||||
issue low-level ``CALLCODE``, returns ``false`` on failure
|
||||
``<address>.delegatecall(...) returns (bool)``:
|
||||
issue low-level ``DELEGATECALL``, returns ``false`` on failure
|
||||
|
||||
For more information, see the section on :ref:`address`.
|
||||
|
||||
.. warning::
|
||||
There are some dangers in using ``send``: The transfer fails if the call stack depth is at 1024
|
||||
(this can always be forced by the caller) and it also fails if the recipient runs out of gas. So in order
|
||||
to make safe Ether transfers, always check the return value of ``send`` or even better:
|
||||
to make safe Ether transfers, always check the return value of ``send``, use ``transfer`` or even better:
|
||||
Use a pattern where the recipient withdraws the money.
|
||||
|
||||
.. index:: this, selfdestruct
|
||||
|
||||
266
docs/using-the-compiler.rst
Normal file
266
docs/using-the-compiler.rst
Normal file
@ -0,0 +1,266 @@
|
||||
******************
|
||||
Using the compiler
|
||||
******************
|
||||
|
||||
.. index:: ! commandline compiler, compiler;commandline, ! solc, ! linker
|
||||
|
||||
.. _commandline-compiler:
|
||||
|
||||
Using the Commandline Compiler
|
||||
******************************
|
||||
|
||||
One of the build targets of the Solidity repository is ``solc``, the solidity commandline compiler.
|
||||
Using ``solc --help`` provides you with an explanation of all options. The compiler can produce various outputs, ranging from simple binaries and assembly over an abstract syntax tree (parse tree) to estimations of gas usage.
|
||||
If you only want to compile a single file, you run it as ``solc --bin sourceFile.sol`` and it will print the binary. Before you deploy your contract, activate the optimizer while compiling using ``solc --optimize --bin sourceFile.sol``. If you want to get some of the more advanced output variants of ``solc``, it is probably better to tell it to output everything to separate files using ``solc -o outputDirectory --bin --ast --asm sourceFile.sol``.
|
||||
|
||||
The commandline compiler will automatically read imported files from the filesystem, but
|
||||
it is also possible to provide path redirects using ``prefix=path`` in the following way:
|
||||
|
||||
::
|
||||
|
||||
solc github.com/ethereum/dapp-bin/=/usr/local/lib/dapp-bin/ =/usr/local/lib/fallback file.sol
|
||||
|
||||
This essentially instructs the compiler to search for anything starting with
|
||||
``github.com/ethereum/dapp-bin/`` under ``/usr/local/lib/dapp-bin`` and if it does not
|
||||
find the file there, it will look at ``/usr/local/lib/fallback`` (the empty prefix
|
||||
always matches). ``solc`` will not read files from the filesystem that lie outside of
|
||||
the remapping targets and outside of the directories where explicitly specified source
|
||||
files reside, so things like ``import "/etc/passwd";`` only work if you add ``=/`` as a remapping.
|
||||
|
||||
If there are multiple matches due to remappings, the one with the longest common prefix is selected.
|
||||
|
||||
If your contracts use :ref:`libraries <libraries>`, you will notice that the bytecode contains substrings of the form ``__LibraryName______``. You can use ``solc`` as a linker meaning that it will insert the library addresses for you at those points:
|
||||
|
||||
Either add ``--libraries "Math:0x12345678901234567890 Heap:0xabcdef0123456"`` to your command to provide an address for each library or store the string in a file (one library per line) and run ``solc`` using ``--libraries fileName``.
|
||||
|
||||
If ``solc`` is called with the option ``--link``, all input files are interpreted to be unlinked binaries (hex-encoded) in the ``__LibraryName____``-format given above and are linked in-place (if the input is read from stdin, it is written to stdout). All options except ``--libraries`` are ignored (including ``-o``) in this case.
|
||||
|
||||
.. _compiler-api:
|
||||
|
||||
Compiler Input and Output JSON Description
|
||||
******************************************
|
||||
|
||||
.. warning::
|
||||
|
||||
This JSON interface is not yet supported by the Solidity compiler, but will be released in a future version.
|
||||
|
||||
These JSON formats are used by the compiler API as well as are available through ``solc``. These are subject to change,
|
||||
some fields are optional (as noted), but it is aimed at to only make backwards compatible changes.
|
||||
|
||||
The compiler API expects a JSON formatted input and outputs the compilation result in a JSON formatted output.
|
||||
|
||||
Comments are of course not permitted and used here only for explanatory purposes.
|
||||
|
||||
Input Description
|
||||
-----------------
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
{
|
||||
// Required: Source code language, such as "Solidity", "serpent", "lll", "assembly", etc.
|
||||
language: "Solidity",
|
||||
// Required
|
||||
sources:
|
||||
{
|
||||
// The keys here are the "global" names of the source files,
|
||||
// imports can use other files via remappings (see below).
|
||||
"myFile.sol":
|
||||
{
|
||||
// Optional: keccak256 hash of the source file
|
||||
// It is used to verify the retrieved content if imported via URLs.
|
||||
"keccak256": "0x123...",
|
||||
// Required (unless "content" is used, see below): URL(s) to the source file.
|
||||
// URL(s) should be imported in this order and the result checked against the
|
||||
// keccak256 hash (if available). If the hash doesn't match or none of the
|
||||
// URL(s) result in success, an error should be raised.
|
||||
"urls":
|
||||
[
|
||||
"bzzr://56ab...",
|
||||
"ipfs://Qma...",
|
||||
"file:///tmp/path/to/file.sol"
|
||||
]
|
||||
},
|
||||
"mortal":
|
||||
{
|
||||
// Optional: keccak256 hash of the source file
|
||||
"keccak256": "0x234...",
|
||||
// Required (unless "urls" is used): literal contents of the source file
|
||||
"content": "contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } }"
|
||||
}
|
||||
},
|
||||
// Optional
|
||||
settings:
|
||||
{
|
||||
// Optional: Sorted list of remappings
|
||||
remappings: [ ":g/dir" ],
|
||||
// Optional: Optimizer settings (enabled defaults to false)
|
||||
optimizer: {
|
||||
enabled: true,
|
||||
runs: 500
|
||||
},
|
||||
// Metadata settings (optional)
|
||||
metadata: {
|
||||
// Use only literal content and not URLs (false by default)
|
||||
useLiteralContent: true
|
||||
},
|
||||
// Addresses of the libraries. If not all libraries are given here, it can result in unlinked objects whose output data is different.
|
||||
libraries: {
|
||||
// The top level key is the the name of the source file where the library is used.
|
||||
// If remappings are used, this source file should match the global path after remappings were applied.
|
||||
// If this key is an empty string, that refers to a global level.
|
||||
"myFile.sol": {
|
||||
"MyLib": "0x123123..."
|
||||
}
|
||||
}
|
||||
// The following can be used to select desired outputs.
|
||||
// If this field is omitted, then the compiler loads and does type checking, but will not generate any outputs apart from errors.
|
||||
// The first level key is the file name and the second is the contract name, where empty contract name refers to the file itself,
|
||||
// while the star refers to all of the contracts.
|
||||
//
|
||||
// The available output types are as follows:
|
||||
// abi - ABI
|
||||
// ast - AST of all source files
|
||||
// why3 - Why3 translated output
|
||||
// devdoc - Developer documentation (natspec)
|
||||
// userdoc - User documentation (natspec)
|
||||
// metadata - Metadata
|
||||
// evm.ir - New assembly format before desugaring
|
||||
// evm.assembly - New assembly format after desugaring
|
||||
// evm.legacyAssemblyJSON - Old-style assembly format in JSON
|
||||
// evm.opcodes - Opcodes list
|
||||
// evm.methodIdentifiers - The list of function hashes
|
||||
// evm.gasEstimates - Function gas estimates
|
||||
// evm.bytecode - Bytecode
|
||||
// evm.deployedBytecode - Deployed bytecode
|
||||
// evm.sourceMap - Source mapping (useful for debugging)
|
||||
// ewasm.wast - eWASM S-expressions format (not supported atm)
|
||||
// ewasm.wasm - eWASM binary format (not supported atm)
|
||||
outputSelection: {
|
||||
// Enable the metadata and bytecode outputs of every single contract.
|
||||
"*": {
|
||||
"*": [ "metadata", "evm.bytecode" ]
|
||||
},
|
||||
// Enable the abi and opcodes output of MyContract defined in file def.
|
||||
"def": {
|
||||
"MyContract": [ "abi", "evm.opcodes" ]
|
||||
},
|
||||
// Enable the source map output of every single contract.
|
||||
"*": {
|
||||
"*": [ "evm.sourceMap" ]
|
||||
},
|
||||
// Enable the AST and Why3 output of every single file.
|
||||
"*": {
|
||||
"": [ "ast", "why3" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Output Description
|
||||
------------------
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
{
|
||||
// Optional: not present if no errors/warnings were encountered
|
||||
errors: [
|
||||
{
|
||||
// Optional: Location within the source file.
|
||||
sourceLocation: {
|
||||
file: "sourceFile.sol",
|
||||
start: 0,
|
||||
end: 100
|
||||
],
|
||||
// Mandatory: Error type, such as "TypeError", "InternalCompilerError", "Exception", etc
|
||||
type: "TypeError",
|
||||
// Mandatory: Component where the error originated, such as "general", "why3", "ewasm", etc.
|
||||
component: "general",
|
||||
// Mandatory ("error" or "warning")
|
||||
severity: "error",
|
||||
// Mandatory
|
||||
message: "Invalid keyword"
|
||||
}
|
||||
],
|
||||
// This contains the file-level outputs. In can be limited/filtered by the outputSelection settings.
|
||||
sources: {
|
||||
"sourceFile.sol": {
|
||||
// Identifier (used in source maps)
|
||||
id: 1,
|
||||
// The AST object
|
||||
ast: {}
|
||||
}
|
||||
},
|
||||
// This contains the contract-level outputs. It can be limited/filtered by the outputSelection settings.
|
||||
contracts: {
|
||||
"sourceFile.sol": {
|
||||
// If the language used has no contract names, this field should equal to an empty string.
|
||||
"ContractName": {
|
||||
// The Ethereum Contract ABI. If empty, it is represented as an empty array.
|
||||
// See https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
|
||||
abi: [],
|
||||
evm: {
|
||||
// Intermediate representation (string)
|
||||
ir: "",
|
||||
// Assembly (string)
|
||||
assembly: "",
|
||||
// Old-style assembly (string)
|
||||
legacyAssemblyJSON: [],
|
||||
// Bytecode and related details.
|
||||
bytecode: {
|
||||
// The bytecode as a hex string.
|
||||
object: "00fe",
|
||||
// The source mapping as a string. See the source mapping definition.
|
||||
sourceMap: "",
|
||||
// If given, this is an unlinked object.
|
||||
linkReferences: {
|
||||
"libraryFile.sol": {
|
||||
// Byte offsets into the bytecode. Linking replaces the 20 bytes located there.
|
||||
"Library1": [
|
||||
{ start: 0, length: 20 },
|
||||
{ start: 200, length: 20 }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
// The same layout as above.
|
||||
deployedBytecode: { },
|
||||
// Opcodes list (string)
|
||||
opcodes: "",
|
||||
// The list of function hashes
|
||||
methodIdentifiers: {
|
||||
"5c19a95c": "delegate(address)",
|
||||
},
|
||||
// Function gas estimates
|
||||
gasEstimates: {
|
||||
creation: {
|
||||
dataCost: 420000,
|
||||
// -1 means infinite (aka. unknown)
|
||||
executionCost: -1
|
||||
},
|
||||
external: {
|
||||
"delegate(address)": 25000
|
||||
},
|
||||
internal: {
|
||||
"heavyLifting()": -1
|
||||
}
|
||||
}
|
||||
},
|
||||
// See the Metadata Output documentation
|
||||
metadata: {},
|
||||
ewasm: {
|
||||
// S-expressions format
|
||||
wast: "",
|
||||
// Binary format (hex string)
|
||||
wasm: ""
|
||||
},
|
||||
// User documentation (natspec)
|
||||
userdoc: {},
|
||||
// Developer documentation (natspec)
|
||||
devdoc: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
// Why3 output (string)
|
||||
why3: ""
|
||||
}
|
||||
@ -54,11 +54,10 @@ class SolidityLexer(RegexLexer):
|
||||
r'(<<|>>>?|==?|!=?|[-<>+*%&\|\^/])=?', Operator, 'slashstartsregex'),
|
||||
(r'[{(\[;,]', Punctuation, 'slashstartsregex'),
|
||||
(r'[})\].]', Punctuation),
|
||||
(r'(for|in|while|do|break|return|continue|switch|case|default|if|else|'
|
||||
r'throw|try|catch|finally|new|delete|typeof|instanceof|void|'
|
||||
r'this|import|mapping|returns|private|public|external|internal|'
|
||||
r'constant|memory|storage)\b', Keyword, 'slashstartsregex'),
|
||||
(r'(var|let|with|function|event|modifier|struct|enum|contract|library)\b', Keyword.Declaration, 'slashstartsregex'),
|
||||
(r'(anonymous|as|assembly|break|constant|continue|do|delete|else|external|for|hex|if|'
|
||||
r'indexed|internal|import|is|mapping|memory|new|payable|public|pragma|'
|
||||
r'private|return|returns|storage|super|this|throw|using|while)\b', Keyword, 'slashstartsregex'),
|
||||
(r'(var|function|event|modifier|struct|enum|contract|library)\b', Keyword.Declaration, 'slashstartsregex'),
|
||||
(r'(bytes|string|address|uint|int|bool|byte|' +
|
||||
'|'.join(
|
||||
['uint%d' % (i + 8) for i in range(0, 256, 8)] +
|
||||
@ -67,16 +66,12 @@ class SolidityLexer(RegexLexer):
|
||||
['ufixed%dx%d' % ((i), (j + 8)) for i in range(0, 256, 8) for j in range(0, 256 - i, 8)] +
|
||||
['fixed%dx%d' % ((i), (j + 8)) for i in range(0, 256, 8) for j in range(0, 256 - i, 8)]
|
||||
) + r')\b', Keyword.Type, 'slashstartsregex'),
|
||||
(r'(abstract|boolean|byte|char|class|const|debugger|double|enum|export|'
|
||||
r'extends|final|float|goto|implements|int|interface|long|native|'
|
||||
r'package|private|protected|public|short|static|super|synchronized|throws|'
|
||||
r'transient|volatile)\b', Keyword.Reserved),
|
||||
(r'(true|false|null|NaN|Infinity|undefined)\b', Keyword.Constant),
|
||||
(r'(Array|Boolean|Date|Error|Function|Math|netscape|'
|
||||
r'Number|Object|Packages|RegExp|String|sun|decodeURI|'
|
||||
r'decodeURIComponent|encodeURI|encodeURIComponent|'
|
||||
r'Error|eval|isFinite|isNaN|parseFloat|parseInt|document|this|'
|
||||
r'window)\b', Name.Builtin),
|
||||
(r'(wei|szabo|finney|ether|seconds|minutes|hours|days|weeks|years)\b', Keyword.Type, 'slashstartsregex'),
|
||||
(r'(abstract|after|case|catch|default|final|in|inline|interface|let|match|'
|
||||
r'null|of|pure|relocatable|static|switch|try|type|typeof|view)\b', Keyword.Reserved),
|
||||
(r'(true|false)\b', Keyword.Constant),
|
||||
(r'(block|msg|tx|now|suicide|selfdestruct|addmod|mulmod|sha3|keccak256|log[0-4]|'
|
||||
r'sha256|ecrecover|ripemd160|assert|revert)', Name.Builtin),
|
||||
(r'[$a-zA-Z_][a-zA-Z0-9_]*', Name.Other),
|
||||
(r'[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?', Number.Float),
|
||||
(r'0x[0-9a-fA-F]+', Number.Hex),
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file ABI.h
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* @file Assertions.h
|
||||
@ -73,27 +73,19 @@ inline bool assertEqualAux(A const& _a, B const& _b, char const* _aStr, char con
|
||||
/// Use it as assertThrow(1 == 1, ExceptionType, "Mathematics is wrong.");
|
||||
/// Do NOT supply an exception object as the second parameter.
|
||||
#define assertThrow(_condition, _ExceptionType, _description) \
|
||||
::dev::assertThrowAux<_ExceptionType>(!!(_condition), _description, __LINE__, __FILE__, ETH_FUNC)
|
||||
do \
|
||||
{ \
|
||||
if (!(_condition)) \
|
||||
::boost::throw_exception( \
|
||||
_ExceptionType() << \
|
||||
::dev::errinfo_comment(_description) << \
|
||||
::boost::throw_function(ETH_FUNC) << \
|
||||
::boost::throw_file(__FILE__) << \
|
||||
::boost::throw_line(__LINE__) \
|
||||
); \
|
||||
} \
|
||||
while (false)
|
||||
|
||||
using errinfo_comment = boost::error_info<struct tag_comment, std::string>;
|
||||
|
||||
template <class _ExceptionType>
|
||||
inline void assertThrowAux(
|
||||
bool _condition,
|
||||
::std::string const& _errorDescription,
|
||||
unsigned _line,
|
||||
char const* _file,
|
||||
char const* _function
|
||||
)
|
||||
{
|
||||
if (!_condition)
|
||||
::boost::throw_exception(
|
||||
_ExceptionType() <<
|
||||
::dev::errinfo_comment(_errorDescription) <<
|
||||
::boost::throw_function(_function) <<
|
||||
::boost::throw_file(_file) <<
|
||||
::boost::throw_line(_line)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file Common.h
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
|
||||
@ -1,26 +1,30 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file CommonData.cpp
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
* @date 2014
|
||||
*/
|
||||
|
||||
#include "CommonData.h"
|
||||
#include "Exceptions.h"
|
||||
#include <libdevcore/CommonData.h>
|
||||
#include <libdevcore/Exceptions.h>
|
||||
#include <libdevcore/SHA3.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
|
||||
@ -95,3 +99,35 @@ bytes dev::fromHex(std::string const& _s, WhenError _throw)
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool dev::passesAddressChecksum(string const& _str, bool _strict)
|
||||
{
|
||||
string s = _str.substr(0, 2) == "0x" ? _str.substr(2) : _str;
|
||||
|
||||
if (s.length() != 40)
|
||||
return false;
|
||||
|
||||
if (!_strict && (
|
||||
_str.find_first_of("abcdef") == string::npos ||
|
||||
_str.find_first_of("ABCDEF") == string::npos
|
||||
))
|
||||
return true;
|
||||
|
||||
h256 hash = keccak256(boost::algorithm::to_lower_copy(s, std::locale::classic()));
|
||||
for (size_t i = 0; i < 40; ++i)
|
||||
{
|
||||
char addressCharacter = s[i];
|
||||
bool lowerCase;
|
||||
if ('a' <= addressCharacter && addressCharacter <= 'f')
|
||||
lowerCase = true;
|
||||
else if ('A' <= addressCharacter && addressCharacter <= 'F')
|
||||
lowerCase = false;
|
||||
else
|
||||
continue;
|
||||
unsigned nibble = (unsigned(hash[i / 2]) >> (4 * (1 - (i % 2)))) & 0xf;
|
||||
if ((nibble >= 8) == lowerCase)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file CommonData.h
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
@ -179,4 +179,9 @@ bool contains(T const& _t, V const& _v)
|
||||
return std::end(_t) != std::find(std::begin(_t), std::end(_t), _v);
|
||||
}
|
||||
|
||||
/// @returns true iff @a _str passess the hex address checksum test.
|
||||
/// @param _strict if false, hex strings with only uppercase or only lowercase letters
|
||||
/// are considered valid.
|
||||
bool passesAddressChecksum(std::string const& _str, bool _strict);
|
||||
|
||||
}
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file CommonIO.cpp
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file CommonIO.h
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file Exceptions.h
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
@ -41,6 +41,9 @@ struct Exception: virtual std::exception, virtual boost::exception
|
||||
Exception(std::string _message = std::string()): m_message(std::move(_message)) {}
|
||||
const char* what() const noexcept override { return m_message.empty() ? std::exception::what() : m_message.c_str(); }
|
||||
|
||||
/// @returns "FileName:LineNumber" referring to the point where the exception was thrown.
|
||||
std::string lineInfo() const;
|
||||
|
||||
private:
|
||||
std::string m_message;
|
||||
};
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file FixedHash.h
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file JSON.h
|
||||
* @date 2016
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file SHA3.cpp
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file SHA3.h
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file SwarmHash.cpp
|
||||
*/
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file SwarmHash.h
|
||||
*/
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file UTF8.cpp
|
||||
* @author Alex Beregszaszi
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file UTF8.h
|
||||
* @author Alex Beregszaszi
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file UndefMacros.h
|
||||
* @author Lefteris <lefteris@ethdev.com>
|
||||
|
||||
@ -40,7 +40,7 @@ void Assembly::append(Assembly const& _a)
|
||||
auto newDeposit = m_deposit + _a.deposit();
|
||||
for (AssemblyItem i: _a.m_items)
|
||||
{
|
||||
if (i.type() == Tag || i.type() == PushTag)
|
||||
if (i.type() == Tag || (i.type() == PushTag && i != errorTag()))
|
||||
i.setData(i.data() + m_usedTags);
|
||||
else if (i.type() == PushSub || i.type() == PushSubSize)
|
||||
i.setData(i.data() + m_subs.size());
|
||||
@ -94,7 +94,10 @@ unsigned Assembly::bytesRequired(unsigned subTagSize) const
|
||||
}
|
||||
}
|
||||
|
||||
string Assembly::locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const
|
||||
namespace
|
||||
{
|
||||
|
||||
string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location)
|
||||
{
|
||||
if (_location.isEmpty() || _sourceCodes.empty() || _location.start >= _location.end || _location.start < 0)
|
||||
return "";
|
||||
@ -115,71 +118,103 @@ string Assembly::locationFromSources(StringMap const& _sourceCodes, SourceLocati
|
||||
return cut;
|
||||
}
|
||||
|
||||
class Functionalizer
|
||||
{
|
||||
public:
|
||||
Functionalizer (ostream& _out, string const& _prefix, StringMap const& _sourceCodes):
|
||||
m_out(_out), m_prefix(_prefix), m_sourceCodes(_sourceCodes)
|
||||
{}
|
||||
|
||||
void feed(AssemblyItem const& _item)
|
||||
{
|
||||
if (!_item.location().isEmpty() && _item.location() != m_location)
|
||||
{
|
||||
flush();
|
||||
m_location = _item.location();
|
||||
printLocation();
|
||||
}
|
||||
if (!(
|
||||
_item.canBeFunctional() &&
|
||||
_item.returnValues() <= 1 &&
|
||||
_item.arguments() <= int(m_pending.size())
|
||||
))
|
||||
{
|
||||
flush();
|
||||
m_out << m_prefix << (_item.type() == Tag ? "" : " ") << _item.toAssemblyText() << endl;
|
||||
return;
|
||||
}
|
||||
string expression = _item.toAssemblyText();
|
||||
if (_item.arguments() > 0)
|
||||
{
|
||||
expression += "(";
|
||||
for (int i = 0; i < _item.arguments(); ++i)
|
||||
{
|
||||
expression += m_pending.back();
|
||||
m_pending.pop_back();
|
||||
if (i + 1 < _item.arguments())
|
||||
expression += ", ";
|
||||
}
|
||||
expression += ")";
|
||||
}
|
||||
|
||||
m_pending.push_back(expression);
|
||||
if (_item.returnValues() != 1)
|
||||
flush();
|
||||
}
|
||||
|
||||
void flush()
|
||||
{
|
||||
for (string const& expression: m_pending)
|
||||
m_out << m_prefix << " " << expression << endl;
|
||||
m_pending.clear();
|
||||
}
|
||||
|
||||
void printLocation()
|
||||
{
|
||||
if (!m_location.sourceName && m_location.isEmpty())
|
||||
return;
|
||||
m_out << m_prefix << " /*";
|
||||
if (m_location.sourceName)
|
||||
m_out << " \"" + *m_location.sourceName + "\"";
|
||||
if (!m_location.isEmpty())
|
||||
m_out << ":" << to_string(m_location.start) + ":" + to_string(m_location.end);
|
||||
m_out << " " << locationFromSources(m_sourceCodes, m_location);
|
||||
m_out << " */" << endl;
|
||||
}
|
||||
|
||||
private:
|
||||
strings m_pending;
|
||||
SourceLocation m_location;
|
||||
|
||||
ostream& m_out;
|
||||
string const& m_prefix;
|
||||
StringMap const& m_sourceCodes;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const
|
||||
{
|
||||
_out << _prefix << ".code:" << endl;
|
||||
for (AssemblyItem const& i: m_items)
|
||||
{
|
||||
_out << _prefix;
|
||||
switch (i.type())
|
||||
{
|
||||
case Operation:
|
||||
_out << " " << instructionInfo(i.instruction()).name << "\t" << i.getJumpTypeAsString();
|
||||
break;
|
||||
case Push:
|
||||
_out << " PUSH" << dec << max<unsigned>(1, dev::bytesRequired(i.data())) << " 0x" << hex << i.data();
|
||||
break;
|
||||
case PushString:
|
||||
_out << " PUSH \"" << m_strings.at((h256)i.data()) << "\"";
|
||||
break;
|
||||
case PushTag:
|
||||
if (i.data() == 0)
|
||||
_out << " PUSH [ErrorTag]";
|
||||
else
|
||||
{
|
||||
size_t subId = i.splitForeignPushTag().first;
|
||||
if (subId == size_t(-1))
|
||||
_out << " PUSH [tag" << dec << i.splitForeignPushTag().second << "]";
|
||||
else
|
||||
_out << " PUSH [tag" << dec << subId << ":" << i.splitForeignPushTag().second << "]";
|
||||
}
|
||||
break;
|
||||
case PushSub:
|
||||
_out << " PUSH [$" << size_t(i.data()) << "]";
|
||||
break;
|
||||
case PushSubSize:
|
||||
_out << " PUSH #[$" << size_t(i.data()) << "]";
|
||||
break;
|
||||
case PushProgramSize:
|
||||
_out << " PUSHSIZE";
|
||||
break;
|
||||
case PushLibraryAddress:
|
||||
_out << " PUSHLIB \"" << m_libraries.at(h256(i.data())) << "\"";
|
||||
break;
|
||||
case Tag:
|
||||
_out << "tag" << dec << i.data() << ": " << endl << _prefix << " JUMPDEST";
|
||||
break;
|
||||
case PushData:
|
||||
_out << " PUSH [" << hex << (unsigned)i.data() << "]";
|
||||
break;
|
||||
default:
|
||||
BOOST_THROW_EXCEPTION(InvalidOpcode());
|
||||
}
|
||||
_out << "\t\t" << locationFromSources(_sourceCodes, i.location()) << endl;
|
||||
}
|
||||
Functionalizer f(_out, _prefix, _sourceCodes);
|
||||
|
||||
for (auto const& i: m_items)
|
||||
f.feed(i);
|
||||
f.flush();
|
||||
|
||||
if (!m_data.empty() || !m_subs.empty())
|
||||
{
|
||||
_out << _prefix << ".data:" << endl;
|
||||
_out << _prefix << "stop" << endl;
|
||||
for (auto const& i: m_data)
|
||||
if (u256(i.first) >= m_subs.size())
|
||||
_out << _prefix << " " << hex << (unsigned)(u256)i.first << ": " << dev::toHex(i.second) << endl;
|
||||
assertThrow(u256(i.first) < m_subs.size(), AssemblyException, "Data not yet implemented.");
|
||||
|
||||
for (size_t i = 0; i < m_subs.size(); ++i)
|
||||
{
|
||||
_out << _prefix << " " << hex << i << ": " << endl;
|
||||
m_subs[i]->stream(_out, _prefix + " ", _sourceCodes);
|
||||
_out << endl << _prefix << "sub_" << i << ": assembly {\n";
|
||||
m_subs[i]->streamAsm(_out, _prefix + " ", _sourceCodes);
|
||||
_out << _prefix << "}" << endl;
|
||||
}
|
||||
}
|
||||
|
||||
return _out;
|
||||
}
|
||||
|
||||
@ -449,7 +484,7 @@ LinkerObject const& Assembly::assemble() const
|
||||
switch (i.type())
|
||||
{
|
||||
case Operation:
|
||||
ret.bytecode.push_back((byte)i.data());
|
||||
ret.bytecode.push_back((byte)i.instruction());
|
||||
break;
|
||||
case PushString:
|
||||
{
|
||||
|
||||
@ -118,7 +118,6 @@ protected:
|
||||
/// returns the replaced tags.
|
||||
std::map<u256, u256> optimiseInternal(bool _enable, bool _isCreation, size_t _runs);
|
||||
|
||||
std::string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const;
|
||||
void donePath() { if (m_totalDeposit != INT_MAX && m_totalDeposit != m_deposit) BOOST_THROW_EXCEPTION(InvalidDeposit()); }
|
||||
unsigned bytesRequired(unsigned subTagSize) const;
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
#include "AssemblyItem.h"
|
||||
#include <libevmasm/SemanticInformation.h>
|
||||
#include <fstream>
|
||||
|
||||
using namespace std;
|
||||
@ -28,19 +29,19 @@ using namespace dev::eth;
|
||||
|
||||
AssemblyItem AssemblyItem::toSubAssemblyTag(size_t _subId) const
|
||||
{
|
||||
assertThrow(m_data < (u256(1) << 64), Exception, "Tag already has subassembly set.");
|
||||
assertThrow(data() < (u256(1) << 64), Exception, "Tag already has subassembly set.");
|
||||
|
||||
assertThrow(m_type == PushTag || m_type == Tag, Exception, "");
|
||||
AssemblyItem r = *this;
|
||||
r.m_type = PushTag;
|
||||
r.setPushTagSubIdAndTag(_subId, size_t(m_data));
|
||||
r.setPushTagSubIdAndTag(_subId, size_t(data()));
|
||||
return r;
|
||||
}
|
||||
|
||||
pair<size_t, size_t> AssemblyItem::splitForeignPushTag() const
|
||||
{
|
||||
assertThrow(m_type == PushTag || m_type == Tag, Exception, "");
|
||||
return make_pair(size_t(m_data / (u256(1) << 64)) - 1, size_t(m_data));
|
||||
return make_pair(size_t((data()) / (u256(1) << 64)) - 1, size_t(data()));
|
||||
}
|
||||
|
||||
void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag)
|
||||
@ -59,7 +60,7 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const
|
||||
case PushString:
|
||||
return 33;
|
||||
case Push:
|
||||
return 1 + max<unsigned>(1, dev::bytesRequired(m_data));
|
||||
return 1 + max<unsigned>(1, dev::bytesRequired(data()));
|
||||
case PushSubSize:
|
||||
case PushProgramSize:
|
||||
return 4; // worst case: a 16MB program
|
||||
@ -75,12 +76,20 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const
|
||||
BOOST_THROW_EXCEPTION(InvalidOpcode());
|
||||
}
|
||||
|
||||
int AssemblyItem::deposit() const
|
||||
int AssemblyItem::arguments() const
|
||||
{
|
||||
if (type() == Operation)
|
||||
return instructionInfo(instruction()).args;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AssemblyItem::returnValues() const
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
case Operation:
|
||||
return instructionInfo(instruction()).ret - instructionInfo(instruction()).args;
|
||||
return instructionInfo(instruction()).ret;
|
||||
case Push:
|
||||
case PushString:
|
||||
case PushTag:
|
||||
@ -97,6 +106,28 @@ int AssemblyItem::deposit() const
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AssemblyItem::canBeFunctional() const
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
case Operation:
|
||||
return !SemanticInformation::isDupInstruction(*this) && !SemanticInformation::isSwapInstruction(*this);
|
||||
case Push:
|
||||
case PushString:
|
||||
case PushTag:
|
||||
case PushData:
|
||||
case PushSub:
|
||||
case PushSubSize:
|
||||
case PushProgramSize:
|
||||
case PushLibraryAddress:
|
||||
return true;
|
||||
case Tag:
|
||||
return false;
|
||||
default:;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
string AssemblyItem::getJumpTypeAsString() const
|
||||
{
|
||||
switch (m_jumpType)
|
||||
@ -111,6 +142,65 @@ string AssemblyItem::getJumpTypeAsString() const
|
||||
}
|
||||
}
|
||||
|
||||
string AssemblyItem::toAssemblyText() const
|
||||
{
|
||||
string text;
|
||||
switch (type())
|
||||
{
|
||||
case Operation:
|
||||
{
|
||||
assertThrow(isValidInstruction(instruction()), AssemblyException, "Invalid instruction.");
|
||||
string name = instructionInfo(instruction()).name;
|
||||
transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); });
|
||||
text = name;
|
||||
break;
|
||||
}
|
||||
case Push:
|
||||
text = toHex(toCompactBigEndian(data(), 1), 1, HexPrefix::Add);
|
||||
break;
|
||||
case PushString:
|
||||
assertThrow(false, AssemblyException, "Push string assembly output not implemented.");
|
||||
break;
|
||||
case PushTag:
|
||||
assertThrow(data() < 0x10000, AssemblyException, "Sub-assembly tags not yet implemented.");
|
||||
text = string("tag_") + to_string(size_t(data()));
|
||||
break;
|
||||
case Tag:
|
||||
assertThrow(data() < 0x10000, AssemblyException, "Sub-assembly tags not yet implemented.");
|
||||
text = string("tag_") + to_string(size_t(data())) + ":";
|
||||
break;
|
||||
case PushData:
|
||||
assertThrow(false, AssemblyException, "Push data not implemented.");
|
||||
break;
|
||||
case PushSub:
|
||||
text = string("dataOffset(sub_") + to_string(size_t(data())) + ")";
|
||||
break;
|
||||
case PushSubSize:
|
||||
text = string("dataSize(sub_") + to_string(size_t(data())) + ")";
|
||||
break;
|
||||
case PushProgramSize:
|
||||
text = string("bytecodeSize");
|
||||
break;
|
||||
case PushLibraryAddress:
|
||||
text = string("linkerSymbol(\"") + toHex(data()) + string("\")");
|
||||
break;
|
||||
case UndefinedItem:
|
||||
assertThrow(false, AssemblyException, "Invalid assembly item.");
|
||||
break;
|
||||
default:
|
||||
BOOST_THROW_EXCEPTION(InvalidOpcode());
|
||||
}
|
||||
if (m_jumpType == JumpType::IntoFunction || m_jumpType == JumpType::OutOfFunction)
|
||||
{
|
||||
text += "\t//";
|
||||
if (m_jumpType == JumpType::IntoFunction)
|
||||
text += " in";
|
||||
else
|
||||
text += " out";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item)
|
||||
{
|
||||
switch (_item.type())
|
||||
|
||||
@ -59,16 +59,22 @@ public:
|
||||
AssemblyItem(u256 _push, SourceLocation const& _location = SourceLocation()):
|
||||
AssemblyItem(Push, _push, _location) { }
|
||||
AssemblyItem(solidity::Instruction _i, SourceLocation const& _location = SourceLocation()):
|
||||
AssemblyItem(Operation, byte(_i), _location) { }
|
||||
m_type(Operation),
|
||||
m_instruction(_i),
|
||||
m_location(_location)
|
||||
{}
|
||||
AssemblyItem(AssemblyItemType _type, u256 _data = 0, SourceLocation const& _location = SourceLocation()):
|
||||
m_type(_type),
|
||||
m_data(_data),
|
||||
m_location(_location)
|
||||
{
|
||||
if (m_type == Operation)
|
||||
m_instruction = Instruction(byte(_data));
|
||||
else
|
||||
m_data = std::make_shared<u256>(_data);
|
||||
}
|
||||
|
||||
AssemblyItem tag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(Tag, m_data); }
|
||||
AssemblyItem pushTag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(PushTag, m_data); }
|
||||
AssemblyItem tag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(Tag, data()); }
|
||||
AssemblyItem pushTag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(PushTag, data()); }
|
||||
/// Converts the tag to a subassembly tag. This has to be called in order to move a tag across assemblies.
|
||||
/// @param _subId the identifier of the subassembly the tag is taken from.
|
||||
AssemblyItem toSubAssemblyTag(size_t _subId) const;
|
||||
@ -79,25 +85,44 @@ public:
|
||||
void setPushTagSubIdAndTag(size_t _subId, size_t _tag);
|
||||
|
||||
AssemblyItemType type() const { return m_type; }
|
||||
u256 const& data() const { return m_data; }
|
||||
void setType(AssemblyItemType const _type) { m_type = _type; }
|
||||
void setData(u256 const& _data) { m_data = _data; }
|
||||
u256 const& data() const { assertThrow(m_type != Operation, Exception, ""); return *m_data; }
|
||||
void setData(u256 const& _data) { assertThrow(m_type != Operation, Exception, ""); m_data = std::make_shared<u256>(_data); }
|
||||
|
||||
/// @returns the instruction of this item (only valid if type() == Operation)
|
||||
Instruction instruction() const { return Instruction(byte(m_data)); }
|
||||
Instruction instruction() const { assertThrow(m_type == Operation, Exception, ""); return m_instruction; }
|
||||
|
||||
/// @returns true if the type and data of the items are equal.
|
||||
bool operator==(AssemblyItem const& _other) const { return m_type == _other.m_type && m_data == _other.m_data; }
|
||||
bool operator==(AssemblyItem const& _other) const
|
||||
{
|
||||
if (type() != _other.type())
|
||||
return false;
|
||||
if (type() == Operation)
|
||||
return instruction() == _other.instruction();
|
||||
else
|
||||
return data() == _other.data();
|
||||
}
|
||||
bool operator!=(AssemblyItem const& _other) const { return !operator==(_other); }
|
||||
/// Less-than operator compatible with operator==.
|
||||
bool operator<(AssemblyItem const& _other) const { return std::tie(m_type, m_data) < std::tie(_other.m_type, _other.m_data); }
|
||||
bool operator<(AssemblyItem const& _other) const
|
||||
{
|
||||
if (type() != _other.type())
|
||||
return type() < _other.type();
|
||||
else if (type() == Operation)
|
||||
return instruction() < _other.instruction();
|
||||
else
|
||||
return data() < _other.data();
|
||||
}
|
||||
|
||||
/// @returns an upper bound for the number of bytes required by this item, assuming that
|
||||
/// the value of a jump tag takes @a _addressLength bytes.
|
||||
unsigned bytesRequired(unsigned _addressLength) const;
|
||||
int deposit() const;
|
||||
int arguments() const;
|
||||
int returnValues() const;
|
||||
int deposit() const { return returnValues() - arguments(); }
|
||||
|
||||
/// @returns true if the assembly item can be used in a functional context.
|
||||
bool canBeFunctional() const;
|
||||
|
||||
bool match(AssemblyItem const& _i) const { return _i.m_type == UndefinedItem || (m_type == _i.m_type && (m_type != Operation || m_data == _i.m_data)); }
|
||||
void setLocation(SourceLocation const& _location) { m_location = _location; }
|
||||
SourceLocation const& location() const { return m_location; }
|
||||
|
||||
@ -108,9 +133,12 @@ public:
|
||||
void setPushedValue(u256 const& _value) const { m_pushedValue = std::make_shared<u256>(_value); }
|
||||
u256 const* pushedValue() const { return m_pushedValue.get(); }
|
||||
|
||||
std::string toAssemblyText() const;
|
||||
|
||||
private:
|
||||
AssemblyItemType m_type;
|
||||
u256 m_data;
|
||||
Instruction m_instruction; ///< Only valid if m_type == Operation
|
||||
std::shared_ptr<u256> m_data; ///< Only valid if m_type != Operation
|
||||
SourceLocation m_location;
|
||||
JumpType m_jumpType = JumpType::Ordinary;
|
||||
/// Pushed value for operations with data to be determined during assembly stage,
|
||||
|
||||
@ -303,7 +303,9 @@ void CSECodeGenerator::generateClassElement(Id _c, bool _allowSequenced)
|
||||
for (auto it: m_classPositions)
|
||||
for (auto p: it.second)
|
||||
if (p > m_stackHeight)
|
||||
{
|
||||
assertThrow(false, OptimizerException, "");
|
||||
}
|
||||
// do some cleanup
|
||||
removeStackTopIfPossible();
|
||||
|
||||
|
||||
@ -38,6 +38,7 @@ unsigned ConstantOptimisationMethod::optimiseConstants(
|
||||
for (AssemblyItem const& item: _items)
|
||||
if (item.type() == Push)
|
||||
pushes[item]++;
|
||||
map<u256, AssemblyItems> pendingReplacements;
|
||||
for (auto it: pushes)
|
||||
{
|
||||
AssemblyItem const& item = it.first;
|
||||
@ -53,17 +54,22 @@ unsigned ConstantOptimisationMethod::optimiseConstants(
|
||||
bigint copyGas = copy.gasNeeded();
|
||||
ComputeMethod compute(params, item.data());
|
||||
bigint computeGas = compute.gasNeeded();
|
||||
AssemblyItems replacement;
|
||||
if (copyGas < literalGas && copyGas < computeGas)
|
||||
{
|
||||
copy.execute(_assembly, _items);
|
||||
replacement = copy.execute(_assembly);
|
||||
optimisations++;
|
||||
}
|
||||
else if (computeGas < literalGas && computeGas < copyGas)
|
||||
else if (computeGas < literalGas && computeGas <= copyGas)
|
||||
{
|
||||
compute.execute(_assembly, _items);
|
||||
replacement = compute.execute(_assembly);
|
||||
optimisations++;
|
||||
}
|
||||
if (!replacement.empty())
|
||||
pendingReplacements[item.data()] = replacement;
|
||||
}
|
||||
if (!pendingReplacements.empty())
|
||||
replaceConstants(_items, pendingReplacements);
|
||||
return optimisations;
|
||||
}
|
||||
|
||||
@ -101,18 +107,24 @@ size_t ConstantOptimisationMethod::bytesRequired(AssemblyItems const& _items)
|
||||
|
||||
void ConstantOptimisationMethod::replaceConstants(
|
||||
AssemblyItems& _items,
|
||||
AssemblyItems const& _replacement
|
||||
) const
|
||||
map<u256, AssemblyItems> const& _replacements
|
||||
)
|
||||
{
|
||||
assertThrow(_items.size() > 0, OptimizerException, "");
|
||||
for (size_t i = 0; i < _items.size(); ++i)
|
||||
AssemblyItems replaced;
|
||||
for (AssemblyItem const& item: _items)
|
||||
{
|
||||
if (_items.at(i) != AssemblyItem(m_value))
|
||||
continue;
|
||||
_items[i] = _replacement[0];
|
||||
_items.insert(_items.begin() + i + 1, _replacement.begin() + 1, _replacement.end());
|
||||
i += _replacement.size() - 1;
|
||||
if (item.type() == Push)
|
||||
{
|
||||
auto it = _replacements.find(item.data());
|
||||
if (it != _replacements.end())
|
||||
{
|
||||
replaced += it->second;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
replaced.push_back(item);
|
||||
}
|
||||
_items = std::move(replaced);
|
||||
}
|
||||
|
||||
bigint LiteralMethod::gasNeeded()
|
||||
@ -128,7 +140,31 @@ bigint LiteralMethod::gasNeeded()
|
||||
CodeCopyMethod::CodeCopyMethod(Params const& _params, u256 const& _value):
|
||||
ConstantOptimisationMethod(_params, _value)
|
||||
{
|
||||
m_copyRoutine = AssemblyItems{
|
||||
}
|
||||
|
||||
bigint CodeCopyMethod::gasNeeded()
|
||||
{
|
||||
return combineGas(
|
||||
// Run gas: we ignore memory increase costs
|
||||
simpleRunGas(copyRoutine()) + GasCosts::copyGas,
|
||||
// Data gas for copy routines: Some bytes are zero, but we ignore them.
|
||||
bytesRequired(copyRoutine()) * (m_params.isCreation ? GasCosts::txDataNonZeroGas : GasCosts::createDataGas),
|
||||
// Data gas for data itself
|
||||
dataGas(toBigEndian(m_value))
|
||||
);
|
||||
}
|
||||
|
||||
AssemblyItems CodeCopyMethod::execute(Assembly& _assembly)
|
||||
{
|
||||
bytes data = toBigEndian(m_value);
|
||||
AssemblyItems actualCopyRoutine = copyRoutine();
|
||||
actualCopyRoutine[4] = _assembly.newData(data);
|
||||
return actualCopyRoutine;
|
||||
}
|
||||
|
||||
AssemblyItems const& CodeCopyMethod::copyRoutine() const
|
||||
{
|
||||
AssemblyItems static copyRoutine{
|
||||
u256(0),
|
||||
Instruction::DUP1,
|
||||
Instruction::MLOAD, // back up memory
|
||||
@ -141,25 +177,7 @@ CodeCopyMethod::CodeCopyMethod(Params const& _params, u256 const& _value):
|
||||
Instruction::SWAP2,
|
||||
Instruction::MSTORE
|
||||
};
|
||||
}
|
||||
|
||||
bigint CodeCopyMethod::gasNeeded()
|
||||
{
|
||||
return combineGas(
|
||||
// Run gas: we ignore memory increase costs
|
||||
simpleRunGas(m_copyRoutine) + GasCosts::copyGas,
|
||||
// Data gas for copy routines: Some bytes are zero, but we ignore them.
|
||||
bytesRequired(m_copyRoutine) * (m_params.isCreation ? GasCosts::txDataNonZeroGas : GasCosts::createDataGas),
|
||||
// Data gas for data itself
|
||||
dataGas(toBigEndian(m_value))
|
||||
);
|
||||
}
|
||||
|
||||
void CodeCopyMethod::execute(Assembly& _assembly, AssemblyItems& _items)
|
||||
{
|
||||
bytes data = toBigEndian(m_value);
|
||||
m_copyRoutine[4] = _assembly.newData(data);
|
||||
replaceConstants(_items, m_copyRoutine);
|
||||
return copyRoutine;
|
||||
}
|
||||
|
||||
AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
|
||||
@ -176,7 +194,7 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
|
||||
// Is not always better, try literal and decomposition method.
|
||||
AssemblyItems routine{u256(_value)};
|
||||
bigint bestGas = gasNeeded(routine);
|
||||
for (unsigned bits = 255; bits > 8; --bits)
|
||||
for (unsigned bits = 255; bits > 8 && m_maxSteps > 0; --bits)
|
||||
{
|
||||
unsigned gapDetector = unsigned(_value >> (bits - 8)) & 0x1ff;
|
||||
if (gapDetector != 0xff && gapDetector != 0x100)
|
||||
@ -201,6 +219,8 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
|
||||
else if (lowerPart < 0)
|
||||
newRoutine.push_back(Instruction::SUB);
|
||||
|
||||
if (m_maxSteps > 0)
|
||||
m_maxSteps--;
|
||||
bigint newGas = gasNeeded(newRoutine);
|
||||
if (newGas < bestGas)
|
||||
{
|
||||
|
||||
@ -60,7 +60,10 @@ public:
|
||||
explicit ConstantOptimisationMethod(Params const& _params, u256 const& _value):
|
||||
m_params(_params), m_value(_value) {}
|
||||
virtual bigint gasNeeded() = 0;
|
||||
virtual void execute(Assembly& _assembly, AssemblyItems& _items) = 0;
|
||||
/// Executes the method, potentially appending to the assembly and returns a vector of
|
||||
/// assembly items the constant should be relpaced with in one sweep.
|
||||
/// If the vector is empty, the constants will not be deleted.
|
||||
virtual AssemblyItems execute(Assembly& _assembly) = 0;
|
||||
|
||||
protected:
|
||||
size_t dataSize() const { return std::max<size_t>(1, dev::bytesRequired(m_value)); }
|
||||
@ -83,8 +86,8 @@ protected:
|
||||
return m_params.runs * _runGas + m_params.multiplicity * _repeatedDataGas + _uniqueDataGas;
|
||||
}
|
||||
|
||||
/// Replaces the constant by the code given in @a _replacement.
|
||||
void replaceConstants(AssemblyItems& _items, AssemblyItems const& _replacement) const;
|
||||
/// Replaces all constants i by the code given in @a _replacement[i].
|
||||
static void replaceConstants(AssemblyItems& _items, std::map<u256, AssemblyItems> const& _replacement);
|
||||
|
||||
Params m_params;
|
||||
u256 const& m_value;
|
||||
@ -100,7 +103,7 @@ public:
|
||||
explicit LiteralMethod(Params const& _params, u256 const& _value):
|
||||
ConstantOptimisationMethod(_params, _value) {}
|
||||
virtual bigint gasNeeded() override;
|
||||
virtual void execute(Assembly&, AssemblyItems&) override {}
|
||||
virtual AssemblyItems execute(Assembly&) override { return AssemblyItems{}; }
|
||||
};
|
||||
|
||||
/**
|
||||
@ -111,10 +114,10 @@ class CodeCopyMethod: public ConstantOptimisationMethod
|
||||
public:
|
||||
explicit CodeCopyMethod(Params const& _params, u256 const& _value);
|
||||
virtual bigint gasNeeded() override;
|
||||
virtual void execute(Assembly& _assembly, AssemblyItems& _items) override;
|
||||
virtual AssemblyItems execute(Assembly& _assembly) override;
|
||||
|
||||
protected:
|
||||
AssemblyItems m_copyRoutine;
|
||||
AssemblyItems const& copyRoutine() const;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -130,9 +133,9 @@ public:
|
||||
}
|
||||
|
||||
virtual bigint gasNeeded() override { return gasNeeded(m_routine); }
|
||||
virtual void execute(Assembly&, AssemblyItems& _items) override
|
||||
virtual AssemblyItems execute(Assembly&) override
|
||||
{
|
||||
replaceConstants(_items, m_routine);
|
||||
return m_routine;
|
||||
}
|
||||
|
||||
protected:
|
||||
@ -140,6 +143,8 @@ protected:
|
||||
AssemblyItems findRepresentation(u256 const& _value);
|
||||
bigint gasNeeded(AssemblyItems const& _routine);
|
||||
|
||||
/// Counter for the complexity of optimization, will stop when it reaches zero.
|
||||
size_t m_maxSteps = 10000;
|
||||
AssemblyItems m_routine;
|
||||
};
|
||||
|
||||
|
||||
@ -47,7 +47,7 @@ struct EVMSchedule
|
||||
unsigned callStipend = 2300;
|
||||
unsigned callValueTransferGas = 9000;
|
||||
unsigned callNewAccountGas = 25000;
|
||||
unsigned suicideRefundGas = 24000;
|
||||
unsigned selfdestructRefundGas = 24000;
|
||||
unsigned memoryGas = 3;
|
||||
unsigned quadCoeffDiv = 512;
|
||||
unsigned createDataGas = 200;
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <libevmasm/Assembly.h>
|
||||
#include <libevmasm/CommonSubexpressionEliminator.h>
|
||||
#include <libevmasm/SimplificationRules.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
@ -40,8 +41,18 @@ bool ExpressionClasses::Expression::operator<(ExpressionClasses::Expression cons
|
||||
assertThrow(!!item && !!_other.item, OptimizerException, "");
|
||||
auto type = item->type();
|
||||
auto otherType = _other.item->type();
|
||||
return std::tie(type, item->data(), arguments, sequenceNumber) <
|
||||
std::tie(otherType, _other.item->data(), _other.arguments, _other.sequenceNumber);
|
||||
if (type != otherType)
|
||||
return type < otherType;
|
||||
else if (type == Operation)
|
||||
{
|
||||
auto instr = item->instruction();
|
||||
auto otherInstr = _other.item->instruction();
|
||||
return std::tie(instr, arguments, sequenceNumber) <
|
||||
std::tie(otherInstr, _other.arguments, _other.sequenceNumber);
|
||||
}
|
||||
else
|
||||
return std::tie(item->data(), arguments, sequenceNumber) <
|
||||
std::tie(_other.item->data(), _other.arguments, _other.sequenceNumber);
|
||||
}
|
||||
|
||||
ExpressionClasses::Id ExpressionClasses::find(
|
||||
@ -170,191 +181,6 @@ string ExpressionClasses::fullDAGToString(ExpressionClasses::Id _id) const
|
||||
return str.str();
|
||||
}
|
||||
|
||||
class Rules: public boost::noncopyable
|
||||
{
|
||||
public:
|
||||
Rules();
|
||||
void resetMatchGroups() { m_matchGroups.clear(); }
|
||||
vector<pair<Pattern, function<Pattern()>>> rules() const { return m_rules; }
|
||||
|
||||
private:
|
||||
using Expression = ExpressionClasses::Expression;
|
||||
map<unsigned, Expression const*> m_matchGroups;
|
||||
vector<pair<Pattern, function<Pattern()>>> m_rules;
|
||||
};
|
||||
|
||||
template <class S> S divWorkaround(S const& _a, S const& _b)
|
||||
{
|
||||
return (S)(bigint(_a) / bigint(_b));
|
||||
}
|
||||
|
||||
template <class S> S modWorkaround(S const& _a, S const& _b)
|
||||
{
|
||||
return (S)(bigint(_a) % bigint(_b));
|
||||
}
|
||||
|
||||
Rules::Rules()
|
||||
{
|
||||
// Multiple occurences of one of these inside one rule must match the same equivalence class.
|
||||
// Constants.
|
||||
Pattern A(Push);
|
||||
Pattern B(Push);
|
||||
Pattern C(Push);
|
||||
// Anything.
|
||||
Pattern X;
|
||||
Pattern Y;
|
||||
Pattern Z;
|
||||
A.setMatchGroup(1, m_matchGroups);
|
||||
B.setMatchGroup(2, m_matchGroups);
|
||||
C.setMatchGroup(3, m_matchGroups);
|
||||
X.setMatchGroup(4, m_matchGroups);
|
||||
Y.setMatchGroup(5, m_matchGroups);
|
||||
Z.setMatchGroup(6, m_matchGroups);
|
||||
|
||||
m_rules = vector<pair<Pattern, function<Pattern()>>>{
|
||||
// arithmetics on constants
|
||||
{{Instruction::ADD, {A, B}}, [=]{ return A.d() + B.d(); }},
|
||||
{{Instruction::MUL, {A, B}}, [=]{ return A.d() * B.d(); }},
|
||||
{{Instruction::SUB, {A, B}}, [=]{ return A.d() - B.d(); }},
|
||||
{{Instruction::DIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : divWorkaround(A.d(), B.d()); }},
|
||||
{{Instruction::SDIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(divWorkaround(u2s(A.d()), u2s(B.d()))); }},
|
||||
{{Instruction::MOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : modWorkaround(A.d(), B.d()); }},
|
||||
{{Instruction::SMOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(modWorkaround(u2s(A.d()), u2s(B.d()))); }},
|
||||
{{Instruction::EXP, {A, B}}, [=]{ return u256(boost::multiprecision::powm(bigint(A.d()), bigint(B.d()), bigint(1) << 256)); }},
|
||||
{{Instruction::NOT, {A}}, [=]{ return ~A.d(); }},
|
||||
{{Instruction::LT, {A, B}}, [=]() { return A.d() < B.d() ? u256(1) : 0; }},
|
||||
{{Instruction::GT, {A, B}}, [=]() -> u256 { return A.d() > B.d() ? 1 : 0; }},
|
||||
{{Instruction::SLT, {A, B}}, [=]() -> u256 { return u2s(A.d()) < u2s(B.d()) ? 1 : 0; }},
|
||||
{{Instruction::SGT, {A, B}}, [=]() -> u256 { return u2s(A.d()) > u2s(B.d()) ? 1 : 0; }},
|
||||
{{Instruction::EQ, {A, B}}, [=]() -> u256 { return A.d() == B.d() ? 1 : 0; }},
|
||||
{{Instruction::ISZERO, {A}}, [=]() -> u256 { return A.d() == 0 ? 1 : 0; }},
|
||||
{{Instruction::AND, {A, B}}, [=]{ return A.d() & B.d(); }},
|
||||
{{Instruction::OR, {A, B}}, [=]{ return A.d() | B.d(); }},
|
||||
{{Instruction::XOR, {A, B}}, [=]{ return A.d() ^ B.d(); }},
|
||||
{{Instruction::BYTE, {A, B}}, [=]{ return A.d() >= 32 ? 0 : (B.d() >> unsigned(8 * (31 - A.d()))) & 0xff; }},
|
||||
{{Instruction::ADDMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) + bigint(B.d())) % C.d()); }},
|
||||
{{Instruction::MULMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) * bigint(B.d())) % C.d()); }},
|
||||
{{Instruction::MULMOD, {A, B, C}}, [=]{ return A.d() * B.d(); }},
|
||||
{{Instruction::SIGNEXTEND, {A, B}}, [=]() -> u256 {
|
||||
if (A.d() >= 31)
|
||||
return B.d();
|
||||
unsigned testBit = unsigned(A.d()) * 8 + 7;
|
||||
u256 mask = (u256(1) << testBit) - 1;
|
||||
return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask);
|
||||
}},
|
||||
|
||||
// invariants involving known constants
|
||||
{{Instruction::ADD, {X, 0}}, [=]{ return X; }},
|
||||
{{Instruction::SUB, {X, 0}}, [=]{ return X; }},
|
||||
{{Instruction::MUL, {X, 1}}, [=]{ return X; }},
|
||||
{{Instruction::DIV, {X, 1}}, [=]{ return X; }},
|
||||
{{Instruction::SDIV, {X, 1}}, [=]{ return X; }},
|
||||
{{Instruction::OR, {X, 0}}, [=]{ return X; }},
|
||||
{{Instruction::XOR, {X, 0}}, [=]{ return X; }},
|
||||
{{Instruction::AND, {X, ~u256(0)}}, [=]{ return X; }},
|
||||
{{Instruction::AND, {X, 0}}, [=]{ return u256(0); }},
|
||||
{{Instruction::MUL, {X, 0}}, [=]{ return u256(0); }},
|
||||
{{Instruction::DIV, {X, 0}}, [=]{ return u256(0); }},
|
||||
{{Instruction::DIV, {0, X}}, [=]{ return u256(0); }},
|
||||
{{Instruction::MOD, {X, 0}}, [=]{ return u256(0); }},
|
||||
{{Instruction::MOD, {0, X}}, [=]{ return u256(0); }},
|
||||
{{Instruction::OR, {X, ~u256(0)}}, [=]{ return ~u256(0); }},
|
||||
{{Instruction::EQ, {X, 0}}, [=]() -> Pattern { return {Instruction::ISZERO, {X}}; } },
|
||||
// operations involving an expression and itself
|
||||
{{Instruction::AND, {X, X}}, [=]{ return X; }},
|
||||
{{Instruction::OR, {X, X}}, [=]{ return X; }},
|
||||
{{Instruction::XOR, {X, X}}, [=]{ return u256(0); }},
|
||||
{{Instruction::SUB, {X, X}}, [=]{ return u256(0); }},
|
||||
{{Instruction::EQ, {X, X}}, [=]{ return u256(1); }},
|
||||
{{Instruction::LT, {X, X}}, [=]{ return u256(0); }},
|
||||
{{Instruction::SLT, {X, X}}, [=]{ return u256(0); }},
|
||||
{{Instruction::GT, {X, X}}, [=]{ return u256(0); }},
|
||||
{{Instruction::SGT, {X, X}}, [=]{ return u256(0); }},
|
||||
{{Instruction::MOD, {X, X}}, [=]{ return u256(0); }},
|
||||
|
||||
{{Instruction::NOT, {{Instruction::NOT, {X}}}}, [=]{ return X; }},
|
||||
{{Instruction::XOR, {{{X}, {Instruction::XOR, {X, Y}}}}}, [=]{ return Y; }},
|
||||
{{Instruction::OR, {{{X}, {Instruction::AND, {X, Y}}}}}, [=]{ return X; }},
|
||||
{{Instruction::AND, {{{X}, {Instruction::OR, {X, Y}}}}}, [=]{ return X; }},
|
||||
{{Instruction::AND, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return u256(0); }},
|
||||
{{Instruction::OR, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return ~u256(0); }},
|
||||
};
|
||||
// Double negation of opcodes with binary result
|
||||
for (auto const& op: vector<Instruction>{
|
||||
Instruction::EQ,
|
||||
Instruction::LT,
|
||||
Instruction::SLT,
|
||||
Instruction::GT,
|
||||
Instruction::SGT
|
||||
})
|
||||
m_rules.push_back({
|
||||
{Instruction::ISZERO, {{Instruction::ISZERO, {{op, {X, Y}}}}}},
|
||||
[=]() -> Pattern { return {op, {X, Y}}; }
|
||||
});
|
||||
m_rules.push_back({
|
||||
{Instruction::ISZERO, {{Instruction::ISZERO, {{Instruction::ISZERO, {X}}}}}},
|
||||
[=]() -> Pattern { return {Instruction::ISZERO, {X}}; }
|
||||
});
|
||||
m_rules.push_back({
|
||||
{Instruction::ISZERO, {{Instruction::XOR, {X, Y}}}},
|
||||
[=]() -> Pattern { return { Instruction::EQ, {X, Y} }; }
|
||||
});
|
||||
// Associative operations
|
||||
for (auto const& opFun: vector<pair<Instruction,function<u256(u256 const&,u256 const&)>>>{
|
||||
{Instruction::ADD, plus<u256>()},
|
||||
{Instruction::MUL, multiplies<u256>()},
|
||||
{Instruction::AND, bit_and<u256>()},
|
||||
{Instruction::OR, bit_or<u256>()},
|
||||
{Instruction::XOR, bit_xor<u256>()}
|
||||
})
|
||||
{
|
||||
auto op = opFun.first;
|
||||
auto fun = opFun.second;
|
||||
// Moving constants to the outside, order matters here!
|
||||
// we need actions that return expressions (or patterns?) here, and we need also reversed rules
|
||||
// (X+A)+B -> X+(A+B)
|
||||
m_rules += vector<pair<Pattern, function<Pattern()>>>{{
|
||||
{op, {{op, {X, A}}, B}},
|
||||
[=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }
|
||||
}, {
|
||||
// X+(Y+A) -> (X+Y)+A
|
||||
{op, {{op, {X, A}}, Y}},
|
||||
[=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }
|
||||
}, {
|
||||
// For now, we still need explicit commutativity for the inner pattern
|
||||
{op, {{op, {A, X}}, B}},
|
||||
[=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }
|
||||
}, {
|
||||
{op, {{op, {A, X}}, Y}},
|
||||
[=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }
|
||||
}};
|
||||
}
|
||||
// move constants across subtractions
|
||||
m_rules += vector<pair<Pattern, function<Pattern()>>>{
|
||||
{
|
||||
// X - A -> X + (-A)
|
||||
{Instruction::SUB, {X, A}},
|
||||
[=]() -> Pattern { return {Instruction::ADD, {X, 0 - A.d()}}; }
|
||||
}, {
|
||||
// (X + A) - Y -> (X - Y) + A
|
||||
{Instruction::SUB, {{Instruction::ADD, {X, A}}, Y}},
|
||||
[=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; }
|
||||
}, {
|
||||
// (A + X) - Y -> (X - Y) + A
|
||||
{Instruction::SUB, {{Instruction::ADD, {A, X}}, Y}},
|
||||
[=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; }
|
||||
}, {
|
||||
// X - (Y + A) -> (X - Y) + (-A)
|
||||
{Instruction::SUB, {X, {Instruction::ADD, {Y, A}}}},
|
||||
[=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; }
|
||||
}, {
|
||||
// X - (A + Y) -> (X - Y) + (-A)
|
||||
{Instruction::SUB, {X, {Instruction::ADD, {A, Y}}}},
|
||||
[=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr, bool _secondRun)
|
||||
{
|
||||
static Rules rules;
|
||||
@ -366,21 +192,17 @@ ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr,
|
||||
)
|
||||
return -1;
|
||||
|
||||
for (auto const& rule: rules.rules())
|
||||
if (auto match = rules.findFirstMatch(_expr, *this))
|
||||
{
|
||||
rules.resetMatchGroups();
|
||||
if (rule.first.matches(_expr, *this))
|
||||
{
|
||||
// Debug info
|
||||
//cout << "Simplifying " << *_expr.item << "(";
|
||||
//for (Id arg: _expr.arguments)
|
||||
// cout << fullDAGToString(arg) << ", ";
|
||||
//cout << ")" << endl;
|
||||
//cout << "with rule " << rule.first.toString() << endl;
|
||||
//ExpressionTemplate t(rule.second());
|
||||
//cout << "to " << rule.second().toString() << endl;
|
||||
return rebuildExpression(ExpressionTemplate(rule.second(), _expr.item->location()));
|
||||
}
|
||||
// Debug info
|
||||
//cout << "Simplifying " << *_expr.item << "(";
|
||||
//for (Id arg: _expr.arguments)
|
||||
// cout << fullDAGToString(arg) << ", ";
|
||||
//cout << ")" << endl;
|
||||
//cout << "with rule " << match->first.toString() << endl;
|
||||
//ExpressionTemplate t(match->second());
|
||||
//cout << "to " << match->second().toString() << endl;
|
||||
return rebuildExpression(ExpressionTemplate(match->second(), _expr.item->location()));
|
||||
}
|
||||
|
||||
if (!_secondRun && _expr.arguments.size() == 2 && SemanticInformation::isCommutativeOperation(*_expr.item))
|
||||
@ -403,122 +225,3 @@ ExpressionClasses::Id ExpressionClasses::rebuildExpression(ExpressionTemplate co
|
||||
arguments.push_back(rebuildExpression(t));
|
||||
return find(_template.item, arguments);
|
||||
}
|
||||
|
||||
|
||||
Pattern::Pattern(Instruction _instruction, std::vector<Pattern> const& _arguments):
|
||||
m_type(Operation),
|
||||
m_requireDataMatch(true),
|
||||
m_data(_instruction),
|
||||
m_arguments(_arguments)
|
||||
{
|
||||
}
|
||||
|
||||
void Pattern::setMatchGroup(unsigned _group, map<unsigned, Expression const*>& _matchGroups)
|
||||
{
|
||||
m_matchGroup = _group;
|
||||
m_matchGroups = &_matchGroups;
|
||||
}
|
||||
|
||||
bool Pattern::matches(Expression const& _expr, ExpressionClasses const& _classes) const
|
||||
{
|
||||
if (!matchesBaseItem(_expr.item))
|
||||
return false;
|
||||
if (m_matchGroup)
|
||||
{
|
||||
if (!m_matchGroups->count(m_matchGroup))
|
||||
(*m_matchGroups)[m_matchGroup] = &_expr;
|
||||
else if ((*m_matchGroups)[m_matchGroup]->id != _expr.id)
|
||||
return false;
|
||||
}
|
||||
assertThrow(m_arguments.size() == 0 || _expr.arguments.size() == m_arguments.size(), OptimizerException, "");
|
||||
for (size_t i = 0; i < m_arguments.size(); ++i)
|
||||
if (!m_arguments[i].matches(_classes.representative(_expr.arguments[i]), _classes))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
AssemblyItem Pattern::toAssemblyItem(SourceLocation const& _location) const
|
||||
{
|
||||
return AssemblyItem(m_type, m_data, _location);
|
||||
}
|
||||
|
||||
string Pattern::toString() const
|
||||
{
|
||||
stringstream s;
|
||||
switch (m_type)
|
||||
{
|
||||
case Operation:
|
||||
s << instructionInfo(Instruction(unsigned(m_data))).name;
|
||||
break;
|
||||
case Push:
|
||||
s << "PUSH " << hex << m_data;
|
||||
break;
|
||||
case UndefinedItem:
|
||||
s << "ANY";
|
||||
break;
|
||||
default:
|
||||
s << "t=" << dec << m_type << " d=" << hex << m_data;
|
||||
break;
|
||||
}
|
||||
if (!m_requireDataMatch)
|
||||
s << " ~";
|
||||
if (m_matchGroup)
|
||||
s << "[" << dec << m_matchGroup << "]";
|
||||
s << "(";
|
||||
for (Pattern const& p: m_arguments)
|
||||
s << p.toString() << ", ";
|
||||
s << ")";
|
||||
return s.str();
|
||||
}
|
||||
|
||||
bool Pattern::matchesBaseItem(AssemblyItem const* _item) const
|
||||
{
|
||||
if (m_type == UndefinedItem)
|
||||
return true;
|
||||
if (!_item)
|
||||
return false;
|
||||
if (m_type != _item->type())
|
||||
return false;
|
||||
if (m_requireDataMatch && m_data != _item->data())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
Pattern::Expression const& Pattern::matchGroupValue() const
|
||||
{
|
||||
assertThrow(m_matchGroup > 0, OptimizerException, "");
|
||||
assertThrow(!!m_matchGroups, OptimizerException, "");
|
||||
assertThrow((*m_matchGroups)[m_matchGroup], OptimizerException, "");
|
||||
return *(*m_matchGroups)[m_matchGroup];
|
||||
}
|
||||
|
||||
|
||||
ExpressionTemplate::ExpressionTemplate(Pattern const& _pattern, SourceLocation const& _location)
|
||||
{
|
||||
if (_pattern.matchGroup())
|
||||
{
|
||||
hasId = true;
|
||||
id = _pattern.id();
|
||||
}
|
||||
else
|
||||
{
|
||||
hasId = false;
|
||||
item = _pattern.toAssemblyItem(_location);
|
||||
}
|
||||
for (auto const& arg: _pattern.arguments())
|
||||
arguments.push_back(ExpressionTemplate(arg, _location));
|
||||
}
|
||||
|
||||
string ExpressionTemplate::toString() const
|
||||
{
|
||||
stringstream s;
|
||||
if (hasId)
|
||||
s << id;
|
||||
else
|
||||
s << item;
|
||||
s << "(";
|
||||
for (auto const& arg: arguments)
|
||||
s << arg.toString();
|
||||
s << ")";
|
||||
return s.str();
|
||||
}
|
||||
|
||||
@ -121,70 +121,5 @@ private:
|
||||
std::vector<std::shared_ptr<AssemblyItem>> m_spareAssemblyItems;
|
||||
};
|
||||
|
||||
/**
|
||||
* Pattern to match against an expression.
|
||||
* Also stores matched expressions to retrieve them later, for constructing new expressions using
|
||||
* ExpressionTemplate.
|
||||
*/
|
||||
class Pattern
|
||||
{
|
||||
public:
|
||||
using Expression = ExpressionClasses::Expression;
|
||||
using Id = ExpressionClasses::Id;
|
||||
|
||||
// Matches a specific constant value.
|
||||
Pattern(unsigned _value): Pattern(u256(_value)) {}
|
||||
// Matches a specific constant value.
|
||||
Pattern(u256 const& _value): m_type(Push), m_requireDataMatch(true), m_data(_value) {}
|
||||
// Matches a specific assembly item type or anything if not given.
|
||||
Pattern(AssemblyItemType _type = UndefinedItem): m_type(_type) {}
|
||||
// Matches a given instruction with given arguments
|
||||
Pattern(Instruction _instruction, std::vector<Pattern> const& _arguments = {});
|
||||
/// Sets this pattern to be part of the match group with the identifier @a _group.
|
||||
/// Inside one rule, all patterns in the same match group have to match expressions from the
|
||||
/// same expression equivalence class.
|
||||
void setMatchGroup(unsigned _group, std::map<unsigned, Expression const*>& _matchGroups);
|
||||
unsigned matchGroup() const { return m_matchGroup; }
|
||||
bool matches(Expression const& _expr, ExpressionClasses const& _classes) const;
|
||||
|
||||
AssemblyItem toAssemblyItem(SourceLocation const& _location) const;
|
||||
std::vector<Pattern> arguments() const { return m_arguments; }
|
||||
|
||||
/// @returns the id of the matched expression if this pattern is part of a match group.
|
||||
Id id() const { return matchGroupValue().id; }
|
||||
/// @returns the data of the matched expression if this pattern is part of a match group.
|
||||
u256 const& d() const { return matchGroupValue().item->data(); }
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
private:
|
||||
bool matchesBaseItem(AssemblyItem const* _item) const;
|
||||
Expression const& matchGroupValue() const;
|
||||
|
||||
AssemblyItemType m_type;
|
||||
bool m_requireDataMatch = false;
|
||||
u256 m_data = 0;
|
||||
std::vector<Pattern> m_arguments;
|
||||
unsigned m_matchGroup = 0;
|
||||
std::map<unsigned, Expression const*>* m_matchGroups = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Template for a new expression that can be built from matched patterns.
|
||||
*/
|
||||
struct ExpressionTemplate
|
||||
{
|
||||
using Expression = ExpressionClasses::Expression;
|
||||
using Id = ExpressionClasses::Id;
|
||||
explicit ExpressionTemplate(Pattern const& _pattern, SourceLocation const& _location);
|
||||
std::string toString() const;
|
||||
bool hasId = false;
|
||||
/// Id of the matched expression, if available.
|
||||
Id id = Id(-1);
|
||||
// Otherwise, assembly item.
|
||||
AssemblyItem item = UndefinedItem;
|
||||
std::vector<ExpressionTemplate> arguments;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,6 +80,7 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
|
||||
gas += GasCosts::sloadGas;
|
||||
break;
|
||||
case Instruction::RETURN:
|
||||
case Instruction::REVERT:
|
||||
gas += memoryGas(0, -1);
|
||||
break;
|
||||
case Instruction::MLOAD:
|
||||
@ -223,14 +224,14 @@ unsigned GasMeter::runGas(Instruction _instruction)
|
||||
|
||||
switch (instructionInfo(_instruction).gasPriceTier)
|
||||
{
|
||||
case 0: return GasCosts::tier0Gas;
|
||||
case 1: return GasCosts::tier1Gas;
|
||||
case 2: return GasCosts::tier2Gas;
|
||||
case 3: return GasCosts::tier3Gas;
|
||||
case 4: return GasCosts::tier4Gas;
|
||||
case 5: return GasCosts::tier5Gas;
|
||||
case 6: return GasCosts::tier6Gas;
|
||||
case 7: return GasCosts::tier7Gas;
|
||||
case Tier::Zero: return GasCosts::tier0Gas;
|
||||
case Tier::Base: return GasCosts::tier1Gas;
|
||||
case Tier::VeryLow: return GasCosts::tier2Gas;
|
||||
case Tier::Low: return GasCosts::tier3Gas;
|
||||
case Tier::Mid: return GasCosts::tier4Gas;
|
||||
case Tier::High: return GasCosts::tier5Gas;
|
||||
case Tier::Ext: return GasCosts::tier6Gas;
|
||||
case Tier::Special: return GasCosts::tier7Gas;
|
||||
default: break;
|
||||
}
|
||||
assertThrow(false, OptimizerException, "Invalid gas tier.");
|
||||
|
||||
@ -61,7 +61,7 @@ namespace GasCosts
|
||||
static unsigned const callStipend = 2300;
|
||||
static unsigned const callValueTransferGas = 9000;
|
||||
static unsigned const callNewAccountGas = 25000;
|
||||
static unsigned const suicideRefundGas = 24000;
|
||||
static unsigned const selfdestructRefundGas = 24000;
|
||||
static unsigned const memoryGas = 3;
|
||||
static unsigned const quadCoeffDiv = 512;
|
||||
static unsigned const createDataGas = 200;
|
||||
|
||||
@ -159,141 +159,145 @@ const std::map<std::string, Instruction> dev::solidity::c_instructions =
|
||||
{ "CALLCODE", Instruction::CALLCODE },
|
||||
{ "RETURN", Instruction::RETURN },
|
||||
{ "DELEGATECALL", Instruction::DELEGATECALL },
|
||||
{ "SUICIDE", Instruction::SUICIDE }
|
||||
{ "REVERT", Instruction::REVERT },
|
||||
{ "INVALID", Instruction::INVALID },
|
||||
{ "SELFDESTRUCT", Instruction::SELFDESTRUCT }
|
||||
};
|
||||
|
||||
static const std::map<Instruction, InstructionInfo> c_instructionInfo =
|
||||
{ // Add, Args, Ret, SideEffects, GasPriceTier
|
||||
{ Instruction::STOP, { "STOP", 0, 0, 0, true, ZeroTier } },
|
||||
{ Instruction::ADD, { "ADD", 0, 2, 1, false, VeryLowTier } },
|
||||
{ Instruction::SUB, { "SUB", 0, 2, 1, false, VeryLowTier } },
|
||||
{ Instruction::MUL, { "MUL", 0, 2, 1, false, LowTier } },
|
||||
{ Instruction::DIV, { "DIV", 0, 2, 1, false, LowTier } },
|
||||
{ Instruction::SDIV, { "SDIV", 0, 2, 1, false, LowTier } },
|
||||
{ Instruction::MOD, { "MOD", 0, 2, 1, false, LowTier } },
|
||||
{ Instruction::SMOD, { "SMOD", 0, 2, 1, false, LowTier } },
|
||||
{ Instruction::EXP, { "EXP", 0, 2, 1, false, SpecialTier } },
|
||||
{ Instruction::NOT, { "NOT", 0, 1, 1, false, VeryLowTier } },
|
||||
{ Instruction::LT, { "LT", 0, 2, 1, false, VeryLowTier } },
|
||||
{ Instruction::GT, { "GT", 0, 2, 1, false, VeryLowTier } },
|
||||
{ Instruction::SLT, { "SLT", 0, 2, 1, false, VeryLowTier } },
|
||||
{ Instruction::SGT, { "SGT", 0, 2, 1, false, VeryLowTier } },
|
||||
{ Instruction::EQ, { "EQ", 0, 2, 1, false, VeryLowTier } },
|
||||
{ Instruction::ISZERO, { "ISZERO", 0, 1, 1, false, VeryLowTier } },
|
||||
{ Instruction::AND, { "AND", 0, 2, 1, false, VeryLowTier } },
|
||||
{ Instruction::OR, { "OR", 0, 2, 1, false, VeryLowTier } },
|
||||
{ Instruction::XOR, { "XOR", 0, 2, 1, false, VeryLowTier } },
|
||||
{ Instruction::BYTE, { "BYTE", 0, 2, 1, false, VeryLowTier } },
|
||||
{ Instruction::ADDMOD, { "ADDMOD", 0, 3, 1, false, MidTier } },
|
||||
{ Instruction::MULMOD, { "MULMOD", 0, 3, 1, false, MidTier } },
|
||||
{ Instruction::SIGNEXTEND, { "SIGNEXTEND", 0, 2, 1, false, LowTier } },
|
||||
{ Instruction::SHA3, { "SHA3", 0, 2, 1, false, SpecialTier } },
|
||||
{ Instruction::ADDRESS, { "ADDRESS", 0, 0, 1, false, BaseTier } },
|
||||
{ Instruction::BALANCE, { "BALANCE", 0, 1, 1, false, ExtTier } },
|
||||
{ Instruction::ORIGIN, { "ORIGIN", 0, 0, 1, false, BaseTier } },
|
||||
{ Instruction::CALLER, { "CALLER", 0, 0, 1, false, BaseTier } },
|
||||
{ Instruction::CALLVALUE, { "CALLVALUE", 0, 0, 1, false, BaseTier } },
|
||||
{ Instruction::CALLDATALOAD,{ "CALLDATALOAD", 0, 1, 1, false, VeryLowTier } },
|
||||
{ Instruction::CALLDATASIZE,{ "CALLDATASIZE", 0, 0, 1, false, BaseTier } },
|
||||
{ Instruction::CALLDATACOPY,{ "CALLDATACOPY", 0, 3, 0, true, VeryLowTier } },
|
||||
{ Instruction::CODESIZE, { "CODESIZE", 0, 0, 1, false, BaseTier } },
|
||||
{ Instruction::CODECOPY, { "CODECOPY", 0, 3, 0, true, VeryLowTier } },
|
||||
{ Instruction::GASPRICE, { "GASPRICE", 0, 0, 1, false, BaseTier } },
|
||||
{ Instruction::EXTCODESIZE, { "EXTCODESIZE", 0, 1, 1, false, ExtTier } },
|
||||
{ Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0, true, ExtTier } },
|
||||
{ Instruction::BLOCKHASH, { "BLOCKHASH", 0, 1, 1, false, ExtTier } },
|
||||
{ Instruction::COINBASE, { "COINBASE", 0, 0, 1, false, BaseTier } },
|
||||
{ Instruction::TIMESTAMP, { "TIMESTAMP", 0, 0, 1, false, BaseTier } },
|
||||
{ Instruction::NUMBER, { "NUMBER", 0, 0, 1, false, BaseTier } },
|
||||
{ Instruction::DIFFICULTY, { "DIFFICULTY", 0, 0, 1, false, BaseTier } },
|
||||
{ Instruction::GASLIMIT, { "GASLIMIT", 0, 0, 1, false, BaseTier } },
|
||||
{ Instruction::POP, { "POP", 0, 1, 0, false, BaseTier } },
|
||||
{ Instruction::MLOAD, { "MLOAD", 0, 1, 1, false, VeryLowTier } },
|
||||
{ Instruction::MSTORE, { "MSTORE", 0, 2, 0, true, VeryLowTier } },
|
||||
{ Instruction::MSTORE8, { "MSTORE8", 0, 2, 0, true, VeryLowTier } },
|
||||
{ Instruction::SLOAD, { "SLOAD", 0, 1, 1, false, SpecialTier } },
|
||||
{ Instruction::SSTORE, { "SSTORE", 0, 2, 0, true, SpecialTier } },
|
||||
{ Instruction::JUMP, { "JUMP", 0, 1, 0, true, MidTier } },
|
||||
{ Instruction::JUMPI, { "JUMPI", 0, 2, 0, true, HighTier } },
|
||||
{ Instruction::PC, { "PC", 0, 0, 1, false, BaseTier } },
|
||||
{ Instruction::MSIZE, { "MSIZE", 0, 0, 1, false, BaseTier } },
|
||||
{ Instruction::GAS, { "GAS", 0, 0, 1, false, BaseTier } },
|
||||
{ Instruction::JUMPDEST, { "JUMPDEST", 0, 0, 0, true, SpecialTier } },
|
||||
{ Instruction::PUSH1, { "PUSH1", 1, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH2, { "PUSH2", 2, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH3, { "PUSH3", 3, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH4, { "PUSH4", 4, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH5, { "PUSH5", 5, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH6, { "PUSH6", 6, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH7, { "PUSH7", 7, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH8, { "PUSH8", 8, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH9, { "PUSH9", 9, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH10, { "PUSH10", 10, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH11, { "PUSH11", 11, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH12, { "PUSH12", 12, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH13, { "PUSH13", 13, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH14, { "PUSH14", 14, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH15, { "PUSH15", 15, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH16, { "PUSH16", 16, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH17, { "PUSH17", 17, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH18, { "PUSH18", 18, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH19, { "PUSH19", 19, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH20, { "PUSH20", 20, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH21, { "PUSH21", 21, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH22, { "PUSH22", 22, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH23, { "PUSH23", 23, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH24, { "PUSH24", 24, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH25, { "PUSH25", 25, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH26, { "PUSH26", 26, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH27, { "PUSH27", 27, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH28, { "PUSH28", 28, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH29, { "PUSH29", 29, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH30, { "PUSH30", 30, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH31, { "PUSH31", 31, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::PUSH32, { "PUSH32", 32, 0, 1, false, VeryLowTier } },
|
||||
{ Instruction::DUP1, { "DUP1", 0, 1, 2, false, VeryLowTier } },
|
||||
{ Instruction::DUP2, { "DUP2", 0, 2, 3, false, VeryLowTier } },
|
||||
{ Instruction::DUP3, { "DUP3", 0, 3, 4, false, VeryLowTier } },
|
||||
{ Instruction::DUP4, { "DUP4", 0, 4, 5, false, VeryLowTier } },
|
||||
{ Instruction::DUP5, { "DUP5", 0, 5, 6, false, VeryLowTier } },
|
||||
{ Instruction::DUP6, { "DUP6", 0, 6, 7, false, VeryLowTier } },
|
||||
{ Instruction::DUP7, { "DUP7", 0, 7, 8, false, VeryLowTier } },
|
||||
{ Instruction::DUP8, { "DUP8", 0, 8, 9, false, VeryLowTier } },
|
||||
{ Instruction::DUP9, { "DUP9", 0, 9, 10, false, VeryLowTier } },
|
||||
{ Instruction::DUP10, { "DUP10", 0, 10, 11, false, VeryLowTier } },
|
||||
{ Instruction::DUP11, { "DUP11", 0, 11, 12, false, VeryLowTier } },
|
||||
{ Instruction::DUP12, { "DUP12", 0, 12, 13, false, VeryLowTier } },
|
||||
{ Instruction::DUP13, { "DUP13", 0, 13, 14, false, VeryLowTier } },
|
||||
{ Instruction::DUP14, { "DUP14", 0, 14, 15, false, VeryLowTier } },
|
||||
{ Instruction::DUP15, { "DUP15", 0, 15, 16, false, VeryLowTier } },
|
||||
{ Instruction::DUP16, { "DUP16", 0, 16, 17, false, VeryLowTier } },
|
||||
{ Instruction::SWAP1, { "SWAP1", 0, 2, 2, false, VeryLowTier } },
|
||||
{ Instruction::SWAP2, { "SWAP2", 0, 3, 3, false, VeryLowTier } },
|
||||
{ Instruction::SWAP3, { "SWAP3", 0, 4, 4, false, VeryLowTier } },
|
||||
{ Instruction::SWAP4, { "SWAP4", 0, 5, 5, false, VeryLowTier } },
|
||||
{ Instruction::SWAP5, { "SWAP5", 0, 6, 6, false, VeryLowTier } },
|
||||
{ Instruction::SWAP6, { "SWAP6", 0, 7, 7, false, VeryLowTier } },
|
||||
{ Instruction::SWAP7, { "SWAP7", 0, 8, 8, false, VeryLowTier } },
|
||||
{ Instruction::SWAP8, { "SWAP8", 0, 9, 9, false, VeryLowTier } },
|
||||
{ Instruction::SWAP9, { "SWAP9", 0, 10, 10, false, VeryLowTier } },
|
||||
{ Instruction::SWAP10, { "SWAP10", 0, 11, 11, false, VeryLowTier } },
|
||||
{ Instruction::SWAP11, { "SWAP11", 0, 12, 12, false, VeryLowTier } },
|
||||
{ Instruction::SWAP12, { "SWAP12", 0, 13, 13, false, VeryLowTier } },
|
||||
{ Instruction::SWAP13, { "SWAP13", 0, 14, 14, false, VeryLowTier } },
|
||||
{ Instruction::SWAP14, { "SWAP14", 0, 15, 15, false, VeryLowTier } },
|
||||
{ Instruction::SWAP15, { "SWAP15", 0, 16, 16, false, VeryLowTier } },
|
||||
{ Instruction::SWAP16, { "SWAP16", 0, 17, 17, false, VeryLowTier } },
|
||||
{ Instruction::LOG0, { "LOG0", 0, 2, 0, true, SpecialTier } },
|
||||
{ Instruction::LOG1, { "LOG1", 0, 3, 0, true, SpecialTier } },
|
||||
{ Instruction::LOG2, { "LOG2", 0, 4, 0, true, SpecialTier } },
|
||||
{ Instruction::LOG3, { "LOG3", 0, 5, 0, true, SpecialTier } },
|
||||
{ Instruction::LOG4, { "LOG4", 0, 6, 0, true, SpecialTier } },
|
||||
{ Instruction::CREATE, { "CREATE", 0, 3, 1, true, SpecialTier } },
|
||||
{ Instruction::CALL, { "CALL", 0, 7, 1, true, SpecialTier } },
|
||||
{ Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, SpecialTier } },
|
||||
{ Instruction::RETURN, { "RETURN", 0, 2, 0, true, ZeroTier } },
|
||||
{ Instruction::DELEGATECALL,{ "DELEGATECALL", 0, 6, 1, true, SpecialTier } },
|
||||
{ Instruction::SUICIDE, { "SUICIDE", 0, 1, 0, true, ZeroTier } }
|
||||
{ Instruction::STOP, { "STOP", 0, 0, 0, true, Tier::Zero } },
|
||||
{ Instruction::ADD, { "ADD", 0, 2, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::SUB, { "SUB", 0, 2, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::MUL, { "MUL", 0, 2, 1, false, Tier::Low } },
|
||||
{ Instruction::DIV, { "DIV", 0, 2, 1, false, Tier::Low } },
|
||||
{ Instruction::SDIV, { "SDIV", 0, 2, 1, false, Tier::Low } },
|
||||
{ Instruction::MOD, { "MOD", 0, 2, 1, false, Tier::Low } },
|
||||
{ Instruction::SMOD, { "SMOD", 0, 2, 1, false, Tier::Low } },
|
||||
{ Instruction::EXP, { "EXP", 0, 2, 1, false, Tier::Special } },
|
||||
{ Instruction::NOT, { "NOT", 0, 1, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::LT, { "LT", 0, 2, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::GT, { "GT", 0, 2, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::SLT, { "SLT", 0, 2, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::SGT, { "SGT", 0, 2, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::EQ, { "EQ", 0, 2, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::ISZERO, { "ISZERO", 0, 1, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::AND, { "AND", 0, 2, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::OR, { "OR", 0, 2, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::XOR, { "XOR", 0, 2, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::BYTE, { "BYTE", 0, 2, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::ADDMOD, { "ADDMOD", 0, 3, 1, false, Tier::Mid } },
|
||||
{ Instruction::MULMOD, { "MULMOD", 0, 3, 1, false, Tier::Mid } },
|
||||
{ Instruction::SIGNEXTEND, { "SIGNEXTEND", 0, 2, 1, false, Tier::Low } },
|
||||
{ Instruction::SHA3, { "SHA3", 0, 2, 1, false, Tier::Special } },
|
||||
{ Instruction::ADDRESS, { "ADDRESS", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::BALANCE, { "BALANCE", 0, 1, 1, false, Tier::Ext } },
|
||||
{ Instruction::ORIGIN, { "ORIGIN", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::CALLER, { "CALLER", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::CALLVALUE, { "CALLVALUE", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::CALLDATALOAD,{ "CALLDATALOAD", 0, 1, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::CALLDATASIZE,{ "CALLDATASIZE", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::CALLDATACOPY,{ "CALLDATACOPY", 0, 3, 0, true, Tier::VeryLow } },
|
||||
{ Instruction::CODESIZE, { "CODESIZE", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::CODECOPY, { "CODECOPY", 0, 3, 0, true, Tier::VeryLow } },
|
||||
{ Instruction::GASPRICE, { "GASPRICE", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::EXTCODESIZE, { "EXTCODESIZE", 0, 1, 1, false, Tier::Ext } },
|
||||
{ Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0, true, Tier::Ext } },
|
||||
{ Instruction::BLOCKHASH, { "BLOCKHASH", 0, 1, 1, false, Tier::Ext } },
|
||||
{ Instruction::COINBASE, { "COINBASE", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::TIMESTAMP, { "TIMESTAMP", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::NUMBER, { "NUMBER", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::DIFFICULTY, { "DIFFICULTY", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::GASLIMIT, { "GASLIMIT", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::POP, { "POP", 0, 1, 0, false, Tier::Base } },
|
||||
{ Instruction::MLOAD, { "MLOAD", 0, 1, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::MSTORE, { "MSTORE", 0, 2, 0, true, Tier::VeryLow } },
|
||||
{ Instruction::MSTORE8, { "MSTORE8", 0, 2, 0, true, Tier::VeryLow } },
|
||||
{ Instruction::SLOAD, { "SLOAD", 0, 1, 1, false, Tier::Special } },
|
||||
{ Instruction::SSTORE, { "SSTORE", 0, 2, 0, true, Tier::Special } },
|
||||
{ Instruction::JUMP, { "JUMP", 0, 1, 0, true, Tier::Mid } },
|
||||
{ Instruction::JUMPI, { "JUMPI", 0, 2, 0, true, Tier::High } },
|
||||
{ Instruction::PC, { "PC", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::MSIZE, { "MSIZE", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::GAS, { "GAS", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::JUMPDEST, { "JUMPDEST", 0, 0, 0, true, Tier::Special } },
|
||||
{ Instruction::PUSH1, { "PUSH1", 1, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH2, { "PUSH2", 2, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH3, { "PUSH3", 3, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH4, { "PUSH4", 4, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH5, { "PUSH5", 5, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH6, { "PUSH6", 6, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH7, { "PUSH7", 7, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH8, { "PUSH8", 8, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH9, { "PUSH9", 9, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH10, { "PUSH10", 10, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH11, { "PUSH11", 11, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH12, { "PUSH12", 12, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH13, { "PUSH13", 13, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH14, { "PUSH14", 14, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH15, { "PUSH15", 15, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH16, { "PUSH16", 16, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH17, { "PUSH17", 17, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH18, { "PUSH18", 18, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH19, { "PUSH19", 19, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH20, { "PUSH20", 20, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH21, { "PUSH21", 21, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH22, { "PUSH22", 22, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH23, { "PUSH23", 23, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH24, { "PUSH24", 24, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH25, { "PUSH25", 25, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH26, { "PUSH26", 26, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH27, { "PUSH27", 27, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH28, { "PUSH28", 28, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH29, { "PUSH29", 29, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH30, { "PUSH30", 30, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH31, { "PUSH31", 31, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::PUSH32, { "PUSH32", 32, 0, 1, false, Tier::VeryLow } },
|
||||
{ Instruction::DUP1, { "DUP1", 0, 1, 2, false, Tier::VeryLow } },
|
||||
{ Instruction::DUP2, { "DUP2", 0, 2, 3, false, Tier::VeryLow } },
|
||||
{ Instruction::DUP3, { "DUP3", 0, 3, 4, false, Tier::VeryLow } },
|
||||
{ Instruction::DUP4, { "DUP4", 0, 4, 5, false, Tier::VeryLow } },
|
||||
{ Instruction::DUP5, { "DUP5", 0, 5, 6, false, Tier::VeryLow } },
|
||||
{ Instruction::DUP6, { "DUP6", 0, 6, 7, false, Tier::VeryLow } },
|
||||
{ Instruction::DUP7, { "DUP7", 0, 7, 8, false, Tier::VeryLow } },
|
||||
{ Instruction::DUP8, { "DUP8", 0, 8, 9, false, Tier::VeryLow } },
|
||||
{ Instruction::DUP9, { "DUP9", 0, 9, 10, false, Tier::VeryLow } },
|
||||
{ Instruction::DUP10, { "DUP10", 0, 10, 11, false, Tier::VeryLow } },
|
||||
{ Instruction::DUP11, { "DUP11", 0, 11, 12, false, Tier::VeryLow } },
|
||||
{ Instruction::DUP12, { "DUP12", 0, 12, 13, false, Tier::VeryLow } },
|
||||
{ Instruction::DUP13, { "DUP13", 0, 13, 14, false, Tier::VeryLow } },
|
||||
{ Instruction::DUP14, { "DUP14", 0, 14, 15, false, Tier::VeryLow } },
|
||||
{ Instruction::DUP15, { "DUP15", 0, 15, 16, false, Tier::VeryLow } },
|
||||
{ Instruction::DUP16, { "DUP16", 0, 16, 17, false, Tier::VeryLow } },
|
||||
{ Instruction::SWAP1, { "SWAP1", 0, 2, 2, false, Tier::VeryLow } },
|
||||
{ Instruction::SWAP2, { "SWAP2", 0, 3, 3, false, Tier::VeryLow } },
|
||||
{ Instruction::SWAP3, { "SWAP3", 0, 4, 4, false, Tier::VeryLow } },
|
||||
{ Instruction::SWAP4, { "SWAP4", 0, 5, 5, false, Tier::VeryLow } },
|
||||
{ Instruction::SWAP5, { "SWAP5", 0, 6, 6, false, Tier::VeryLow } },
|
||||
{ Instruction::SWAP6, { "SWAP6", 0, 7, 7, false, Tier::VeryLow } },
|
||||
{ Instruction::SWAP7, { "SWAP7", 0, 8, 8, false, Tier::VeryLow } },
|
||||
{ Instruction::SWAP8, { "SWAP8", 0, 9, 9, false, Tier::VeryLow } },
|
||||
{ Instruction::SWAP9, { "SWAP9", 0, 10, 10, false, Tier::VeryLow } },
|
||||
{ Instruction::SWAP10, { "SWAP10", 0, 11, 11, false, Tier::VeryLow } },
|
||||
{ Instruction::SWAP11, { "SWAP11", 0, 12, 12, false, Tier::VeryLow } },
|
||||
{ Instruction::SWAP12, { "SWAP12", 0, 13, 13, false, Tier::VeryLow } },
|
||||
{ Instruction::SWAP13, { "SWAP13", 0, 14, 14, false, Tier::VeryLow } },
|
||||
{ Instruction::SWAP14, { "SWAP14", 0, 15, 15, false, Tier::VeryLow } },
|
||||
{ Instruction::SWAP15, { "SWAP15", 0, 16, 16, false, Tier::VeryLow } },
|
||||
{ Instruction::SWAP16, { "SWAP16", 0, 17, 17, false, Tier::VeryLow } },
|
||||
{ Instruction::LOG0, { "LOG0", 0, 2, 0, true, Tier::Special } },
|
||||
{ Instruction::LOG1, { "LOG1", 0, 3, 0, true, Tier::Special } },
|
||||
{ Instruction::LOG2, { "LOG2", 0, 4, 0, true, Tier::Special } },
|
||||
{ Instruction::LOG3, { "LOG3", 0, 5, 0, true, Tier::Special } },
|
||||
{ Instruction::LOG4, { "LOG4", 0, 6, 0, true, Tier::Special } },
|
||||
{ Instruction::CREATE, { "CREATE", 0, 3, 1, true, Tier::Special } },
|
||||
{ Instruction::CALL, { "CALL", 0, 7, 1, true, Tier::Special } },
|
||||
{ Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, Tier::Special } },
|
||||
{ Instruction::RETURN, { "RETURN", 0, 2, 0, true, Tier::Zero } },
|
||||
{ Instruction::DELEGATECALL, { "DELEGATECALL", 0, 6, 1, true, Tier::Special } },
|
||||
{ Instruction::REVERT, { "REVERT", 0, 2, 0, true, Tier::Zero } },
|
||||
{ Instruction::INVALID, { "INVALID", 0, 0, 0, true, Tier::Zero } },
|
||||
{ Instruction::SELFDESTRUCT, { "SELFDESTRUCT", 0, 1, 0, true, Tier::Zero } }
|
||||
};
|
||||
|
||||
void dev::solidity::eachInstruction(
|
||||
@ -343,7 +347,7 @@ InstructionInfo dev::solidity::instructionInfo(Instruction _inst)
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return InstructionInfo({"<INVALID_INSTRUCTION: " + toString((unsigned)_inst) + ">", 0, 0, 0, false, InvalidTier});
|
||||
return InstructionInfo({"<INVALID_INSTRUCTION: " + toString((unsigned)_inst) + ">", 0, 0, 0, false, Tier::Invalid});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -176,7 +176,10 @@ enum class Instruction: uint8_t
|
||||
CALLCODE, ///< message-call with another account's code only
|
||||
RETURN, ///< halt execution returning output data
|
||||
DELEGATECALL, ///< like CALLCODE but keeps caller's value and sender
|
||||
SUICIDE = 0xff ///< halt execution and register account for later deletion
|
||||
|
||||
REVERT = 0xfd, ///< halt execution, revert state and return output data
|
||||
INVALID = 0xfe, ///< invalid instruction for expressing runtime errors (e.g., division-by-zero)
|
||||
SELFDESTRUCT = 0xff ///< halt execution and register account for later deletion
|
||||
};
|
||||
|
||||
/// @returns the number of PUSH Instruction _inst
|
||||
@ -200,42 +203,42 @@ inline unsigned getSwapNumber(Instruction _inst)
|
||||
/// @returns the PUSH<_number> instruction
|
||||
inline Instruction pushInstruction(unsigned _number)
|
||||
{
|
||||
assertThrow(1 <= _number && _number <= 32, InvalidOpcode, "Invalid PUSH instruction requested.");
|
||||
assertThrow(1 <= _number && _number <= 32, InvalidOpcode, std::string("Invalid PUSH instruction requested (") + std::to_string(_number) + ").");
|
||||
return Instruction(unsigned(Instruction::PUSH1) + _number - 1);
|
||||
}
|
||||
|
||||
/// @returns the DUP<_number> instruction
|
||||
inline Instruction dupInstruction(unsigned _number)
|
||||
{
|
||||
assertThrow(1 <= _number && _number <= 16, InvalidOpcode, "Invalid DUP instruction requested.");
|
||||
assertThrow(1 <= _number && _number <= 16, InvalidOpcode, std::string("Invalid DUP instruction requested (") + std::to_string(_number) + ").");
|
||||
return Instruction(unsigned(Instruction::DUP1) + _number - 1);
|
||||
}
|
||||
|
||||
/// @returns the SWAP<_number> instruction
|
||||
inline Instruction swapInstruction(unsigned _number)
|
||||
{
|
||||
assertThrow(1 <= _number && _number <= 16, InvalidOpcode, "Invalid SWAP instruction requested.");
|
||||
assertThrow(1 <= _number && _number <= 16, InvalidOpcode, std::string("Invalid SWAP instruction requested (") + std::to_string(_number) + ").");
|
||||
return Instruction(unsigned(Instruction::SWAP1) + _number - 1);
|
||||
}
|
||||
|
||||
/// @returns the LOG<_number> instruction
|
||||
inline Instruction logInstruction(unsigned _number)
|
||||
{
|
||||
assertThrow(_number <= 4, InvalidOpcode, "Invalid LOG instruction requested.");
|
||||
assertThrow(_number <= 4, InvalidOpcode, std::string("Invalid LOG instruction requested (") + std::to_string(_number) + ").");
|
||||
return Instruction(unsigned(Instruction::LOG0) + _number);
|
||||
}
|
||||
|
||||
enum Tier
|
||||
enum class Tier : unsigned
|
||||
{
|
||||
ZeroTier = 0, // 0, Zero
|
||||
BaseTier, // 2, Quick
|
||||
VeryLowTier, // 3, Fastest
|
||||
LowTier, // 5, Fast
|
||||
MidTier, // 8, Mid
|
||||
HighTier, // 10, Slow
|
||||
ExtTier, // 20, Ext
|
||||
SpecialTier, // multiparam or otherwise special
|
||||
InvalidTier // Invalid.
|
||||
Zero = 0, // 0, Zero
|
||||
Base, // 2, Quick
|
||||
VeryLow, // 3, Fastest
|
||||
Low, // 5, Fast
|
||||
Mid, // 8, Mid
|
||||
High, // 10, Slow
|
||||
Ext, // 20, Ext
|
||||
Special, // multiparam or otherwise special
|
||||
Invalid // Invalid.
|
||||
};
|
||||
|
||||
/// Information structure for a particular instruction.
|
||||
@ -246,7 +249,7 @@ struct InstructionInfo
|
||||
int args; ///< Number of items required on the stack for this instruction (and, for the purposes of ret, the number taken from the stack).
|
||||
int ret; ///< Number of items placed (back) on the stack by this instruction, assuming args items were removed.
|
||||
bool sideEffects; ///< false if the only effect on the execution environment (apart from gas usage) is a change to a topmost segment of the stack
|
||||
int gasPriceTier; ///< Tier for gas pricing.
|
||||
Tier gasPriceTier; ///< Tier for gas pricing.
|
||||
};
|
||||
|
||||
/// Information on all the instructions.
|
||||
|
||||
@ -37,13 +37,10 @@ void LinkerObject::link(map<string, h160> const& _libraryAddresses)
|
||||
{
|
||||
std::map<size_t, std::string> remainingRefs;
|
||||
for (auto const& linkRef: linkReferences)
|
||||
{
|
||||
auto it = _libraryAddresses.find(linkRef.second);
|
||||
if (it == _libraryAddresses.end())
|
||||
remainingRefs.insert(linkRef);
|
||||
if (h160 const* address = matchLibrary(linkRef.second, _libraryAddresses))
|
||||
address->ref().copyTo(ref(bytecode).cropped(linkRef.first, 20));
|
||||
else
|
||||
it->second.ref().copyTo(ref(bytecode).cropped(linkRef.first, 20));
|
||||
}
|
||||
remainingRefs.insert(linkRef);
|
||||
linkReferences.swap(remainingRefs);
|
||||
}
|
||||
|
||||
@ -60,3 +57,23 @@ string LinkerObject::toHex() const
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
h160 const*
|
||||
LinkerObject::matchLibrary(
|
||||
string const& _linkRefName,
|
||||
map<string, h160> const& _libraryAddresses
|
||||
)
|
||||
{
|
||||
auto it = _libraryAddresses.find(_linkRefName);
|
||||
if (it != _libraryAddresses.end())
|
||||
return &it->second;
|
||||
// If the user did not supply a fully qualified library name,
|
||||
// try to match only the simple libary name
|
||||
size_t colon = _linkRefName.find(':');
|
||||
if (colon == string::npos)
|
||||
return nullptr;
|
||||
it = _libraryAddresses.find(_linkRefName.substr(colon + 1));
|
||||
if (it != _libraryAddresses.end())
|
||||
return &it->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -49,6 +49,12 @@ struct LinkerObject
|
||||
/// @returns a hex representation of the bytecode of the given object, replacing unlinked
|
||||
/// addresses by placeholders.
|
||||
std::string toHex() const;
|
||||
|
||||
private:
|
||||
static h160 const* matchLibrary(
|
||||
std::string const& _linkRefName,
|
||||
std::map<std::string, h160> const& _libraryAddresses
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -199,7 +199,9 @@ struct UnreachableCode
|
||||
it[0] != Instruction::JUMP &&
|
||||
it[0] != Instruction::RETURN &&
|
||||
it[0] != Instruction::STOP &&
|
||||
it[0] != Instruction::SUICIDE
|
||||
it[0] != Instruction::INVALID &&
|
||||
it[0] != Instruction::SELFDESTRUCT &&
|
||||
it[0] != Instruction::REVERT
|
||||
)
|
||||
return false;
|
||||
|
||||
|
||||
@ -116,8 +116,10 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item)
|
||||
case Instruction::JUMP:
|
||||
case Instruction::JUMPI:
|
||||
case Instruction::RETURN:
|
||||
case Instruction::SUICIDE:
|
||||
case Instruction::SELFDESTRUCT:
|
||||
case Instruction::STOP:
|
||||
case Instruction::INVALID:
|
||||
case Instruction::REVERT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
||||
370
libevmasm/SimplificationRules.cpp
Normal file
370
libevmasm/SimplificationRules.cpp
Normal file
@ -0,0 +1,370 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/**
|
||||
* @file ExpressionClasses.cpp
|
||||
* @author Christian <c@ethdev.com>
|
||||
* @date 2015
|
||||
* Container for equivalence classes of expressions for use in common subexpression elimination.
|
||||
*/
|
||||
|
||||
#include <libevmasm/ExpressionClasses.h>
|
||||
#include <utility>
|
||||
#include <tuple>
|
||||
#include <functional>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <libevmasm/Assembly.h>
|
||||
#include <libevmasm/CommonSubexpressionEliminator.h>
|
||||
#include <libevmasm/SimplificationRules.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::eth;
|
||||
|
||||
|
||||
pair<Pattern, function<Pattern()> > const* Rules::findFirstMatch(
|
||||
Expression const& _expr,
|
||||
ExpressionClasses const& _classes
|
||||
)
|
||||
{
|
||||
resetMatchGroups();
|
||||
|
||||
assertThrow(_expr.item, OptimizerException, "");
|
||||
for (auto const& rule: m_rules[byte(_expr.item->instruction())])
|
||||
{
|
||||
if (rule.first.matches(_expr, _classes))
|
||||
return &rule;
|
||||
resetMatchGroups();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Rules::addRules(std::vector<std::pair<Pattern, std::function<Pattern ()> > > const& _rules)
|
||||
{
|
||||
for (auto const& r: _rules)
|
||||
addRule(r);
|
||||
}
|
||||
|
||||
void Rules::addRule(std::pair<Pattern, std::function<Pattern()> > const& _rule)
|
||||
{
|
||||
m_rules[byte(_rule.first.instruction())].push_back(_rule);
|
||||
}
|
||||
|
||||
template <class S> S divWorkaround(S const& _a, S const& _b)
|
||||
{
|
||||
return (S)(bigint(_a) / bigint(_b));
|
||||
}
|
||||
|
||||
template <class S> S modWorkaround(S const& _a, S const& _b)
|
||||
{
|
||||
return (S)(bigint(_a) % bigint(_b));
|
||||
}
|
||||
|
||||
Rules::Rules()
|
||||
{
|
||||
// Multiple occurences of one of these inside one rule must match the same equivalence class.
|
||||
// Constants.
|
||||
Pattern A(Push);
|
||||
Pattern B(Push);
|
||||
Pattern C(Push);
|
||||
// Anything.
|
||||
Pattern X;
|
||||
Pattern Y;
|
||||
Pattern Z;
|
||||
A.setMatchGroup(1, m_matchGroups);
|
||||
B.setMatchGroup(2, m_matchGroups);
|
||||
C.setMatchGroup(3, m_matchGroups);
|
||||
X.setMatchGroup(4, m_matchGroups);
|
||||
Y.setMatchGroup(5, m_matchGroups);
|
||||
Z.setMatchGroup(6, m_matchGroups);
|
||||
|
||||
addRules(vector<pair<Pattern, function<Pattern()>>>{
|
||||
// arithmetics on constants
|
||||
{{Instruction::ADD, {A, B}}, [=]{ return A.d() + B.d(); }},
|
||||
{{Instruction::MUL, {A, B}}, [=]{ return A.d() * B.d(); }},
|
||||
{{Instruction::SUB, {A, B}}, [=]{ return A.d() - B.d(); }},
|
||||
{{Instruction::DIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : divWorkaround(A.d(), B.d()); }},
|
||||
{{Instruction::SDIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(divWorkaround(u2s(A.d()), u2s(B.d()))); }},
|
||||
{{Instruction::MOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : modWorkaround(A.d(), B.d()); }},
|
||||
{{Instruction::SMOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(modWorkaround(u2s(A.d()), u2s(B.d()))); }},
|
||||
{{Instruction::EXP, {A, B}}, [=]{ return u256(boost::multiprecision::powm(bigint(A.d()), bigint(B.d()), bigint(1) << 256)); }},
|
||||
{{Instruction::NOT, {A}}, [=]{ return ~A.d(); }},
|
||||
{{Instruction::LT, {A, B}}, [=]() { return A.d() < B.d() ? u256(1) : 0; }},
|
||||
{{Instruction::GT, {A, B}}, [=]() -> u256 { return A.d() > B.d() ? 1 : 0; }},
|
||||
{{Instruction::SLT, {A, B}}, [=]() -> u256 { return u2s(A.d()) < u2s(B.d()) ? 1 : 0; }},
|
||||
{{Instruction::SGT, {A, B}}, [=]() -> u256 { return u2s(A.d()) > u2s(B.d()) ? 1 : 0; }},
|
||||
{{Instruction::EQ, {A, B}}, [=]() -> u256 { return A.d() == B.d() ? 1 : 0; }},
|
||||
{{Instruction::ISZERO, {A}}, [=]() -> u256 { return A.d() == 0 ? 1 : 0; }},
|
||||
{{Instruction::AND, {A, B}}, [=]{ return A.d() & B.d(); }},
|
||||
{{Instruction::OR, {A, B}}, [=]{ return A.d() | B.d(); }},
|
||||
{{Instruction::XOR, {A, B}}, [=]{ return A.d() ^ B.d(); }},
|
||||
{{Instruction::BYTE, {A, B}}, [=]{ return A.d() >= 32 ? 0 : (B.d() >> unsigned(8 * (31 - A.d()))) & 0xff; }},
|
||||
{{Instruction::ADDMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) + bigint(B.d())) % C.d()); }},
|
||||
{{Instruction::MULMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) * bigint(B.d())) % C.d()); }},
|
||||
{{Instruction::MULMOD, {A, B, C}}, [=]{ return A.d() * B.d(); }},
|
||||
{{Instruction::SIGNEXTEND, {A, B}}, [=]() -> u256 {
|
||||
if (A.d() >= 31)
|
||||
return B.d();
|
||||
unsigned testBit = unsigned(A.d()) * 8 + 7;
|
||||
u256 mask = (u256(1) << testBit) - 1;
|
||||
return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask);
|
||||
}},
|
||||
|
||||
// invariants involving known constants
|
||||
{{Instruction::ADD, {X, 0}}, [=]{ return X; }},
|
||||
{{Instruction::SUB, {X, 0}}, [=]{ return X; }},
|
||||
{{Instruction::MUL, {X, 1}}, [=]{ return X; }},
|
||||
{{Instruction::DIV, {X, 1}}, [=]{ return X; }},
|
||||
{{Instruction::SDIV, {X, 1}}, [=]{ return X; }},
|
||||
{{Instruction::OR, {X, 0}}, [=]{ return X; }},
|
||||
{{Instruction::XOR, {X, 0}}, [=]{ return X; }},
|
||||
{{Instruction::AND, {X, ~u256(0)}}, [=]{ return X; }},
|
||||
{{Instruction::AND, {X, 0}}, [=]{ return u256(0); }},
|
||||
{{Instruction::MUL, {X, 0}}, [=]{ return u256(0); }},
|
||||
{{Instruction::DIV, {X, 0}}, [=]{ return u256(0); }},
|
||||
{{Instruction::DIV, {0, X}}, [=]{ return u256(0); }},
|
||||
{{Instruction::MOD, {X, 0}}, [=]{ return u256(0); }},
|
||||
{{Instruction::MOD, {0, X}}, [=]{ return u256(0); }},
|
||||
{{Instruction::OR, {X, ~u256(0)}}, [=]{ return ~u256(0); }},
|
||||
{{Instruction::EQ, {X, 0}}, [=]() -> Pattern { return {Instruction::ISZERO, {X}}; } },
|
||||
// operations involving an expression and itself
|
||||
{{Instruction::AND, {X, X}}, [=]{ return X; }},
|
||||
{{Instruction::OR, {X, X}}, [=]{ return X; }},
|
||||
{{Instruction::XOR, {X, X}}, [=]{ return u256(0); }},
|
||||
{{Instruction::SUB, {X, X}}, [=]{ return u256(0); }},
|
||||
{{Instruction::EQ, {X, X}}, [=]{ return u256(1); }},
|
||||
{{Instruction::LT, {X, X}}, [=]{ return u256(0); }},
|
||||
{{Instruction::SLT, {X, X}}, [=]{ return u256(0); }},
|
||||
{{Instruction::GT, {X, X}}, [=]{ return u256(0); }},
|
||||
{{Instruction::SGT, {X, X}}, [=]{ return u256(0); }},
|
||||
{{Instruction::MOD, {X, X}}, [=]{ return u256(0); }},
|
||||
|
||||
{{Instruction::NOT, {{Instruction::NOT, {X}}}}, [=]{ return X; }},
|
||||
{{Instruction::XOR, {{{X}, {Instruction::XOR, {X, Y}}}}}, [=]{ return Y; }},
|
||||
{{Instruction::OR, {{{X}, {Instruction::AND, {X, Y}}}}}, [=]{ return X; }},
|
||||
{{Instruction::AND, {{{X}, {Instruction::OR, {X, Y}}}}}, [=]{ return X; }},
|
||||
{{Instruction::AND, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return u256(0); }},
|
||||
{{Instruction::OR, {{{X}, {Instruction::NOT, {X}}}}}, [=]{ return ~u256(0); }},
|
||||
});
|
||||
// Double negation of opcodes with binary result
|
||||
for (auto const& op: vector<Instruction>{
|
||||
Instruction::EQ,
|
||||
Instruction::LT,
|
||||
Instruction::SLT,
|
||||
Instruction::GT,
|
||||
Instruction::SGT
|
||||
})
|
||||
addRule({
|
||||
{Instruction::ISZERO, {{Instruction::ISZERO, {{op, {X, Y}}}}}},
|
||||
[=]() -> Pattern { return {op, {X, Y}}; }
|
||||
});
|
||||
addRule({
|
||||
{Instruction::ISZERO, {{Instruction::ISZERO, {{Instruction::ISZERO, {X}}}}}},
|
||||
[=]() -> Pattern { return {Instruction::ISZERO, {X}}; }
|
||||
});
|
||||
addRule({
|
||||
{Instruction::ISZERO, {{Instruction::XOR, {X, Y}}}},
|
||||
[=]() -> Pattern { return { Instruction::EQ, {X, Y} }; }
|
||||
});
|
||||
// Associative operations
|
||||
for (auto const& opFun: vector<pair<Instruction,function<u256(u256 const&,u256 const&)>>>{
|
||||
{Instruction::ADD, plus<u256>()},
|
||||
{Instruction::MUL, multiplies<u256>()},
|
||||
{Instruction::AND, bit_and<u256>()},
|
||||
{Instruction::OR, bit_or<u256>()},
|
||||
{Instruction::XOR, bit_xor<u256>()}
|
||||
})
|
||||
{
|
||||
auto op = opFun.first;
|
||||
auto fun = opFun.second;
|
||||
// Moving constants to the outside, order matters here!
|
||||
// we need actions that return expressions (or patterns?) here, and we need also reversed rules
|
||||
// (X+A)+B -> X+(A+B)
|
||||
addRules(vector<pair<Pattern, function<Pattern()>>>{{
|
||||
{op, {{op, {X, A}}, B}},
|
||||
[=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }
|
||||
}, {
|
||||
// X+(Y+A) -> (X+Y)+A
|
||||
{op, {{op, {X, A}}, Y}},
|
||||
[=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }
|
||||
}, {
|
||||
// For now, we still need explicit commutativity for the inner pattern
|
||||
{op, {{op, {A, X}}, B}},
|
||||
[=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }
|
||||
}, {
|
||||
{op, {{op, {A, X}}, Y}},
|
||||
[=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }
|
||||
}});
|
||||
}
|
||||
// move constants across subtractions
|
||||
addRules(vector<pair<Pattern, function<Pattern()>>>{
|
||||
{
|
||||
// X - A -> X + (-A)
|
||||
{Instruction::SUB, {X, A}},
|
||||
[=]() -> Pattern { return {Instruction::ADD, {X, 0 - A.d()}}; }
|
||||
}, {
|
||||
// (X + A) - Y -> (X - Y) + A
|
||||
{Instruction::SUB, {{Instruction::ADD, {X, A}}, Y}},
|
||||
[=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; }
|
||||
}, {
|
||||
// (A + X) - Y -> (X - Y) + A
|
||||
{Instruction::SUB, {{Instruction::ADD, {A, X}}, Y}},
|
||||
[=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; }
|
||||
}, {
|
||||
// X - (Y + A) -> (X - Y) + (-A)
|
||||
{Instruction::SUB, {X, {Instruction::ADD, {Y, A}}}},
|
||||
[=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; }
|
||||
}, {
|
||||
// X - (A + Y) -> (X - Y) + (-A)
|
||||
{Instruction::SUB, {X, {Instruction::ADD, {A, Y}}}},
|
||||
[=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Pattern::Pattern(Instruction _instruction, std::vector<Pattern> const& _arguments):
|
||||
m_type(Operation),
|
||||
m_instruction(_instruction),
|
||||
m_arguments(_arguments)
|
||||
{
|
||||
}
|
||||
|
||||
void Pattern::setMatchGroup(unsigned _group, map<unsigned, Expression const*>& _matchGroups)
|
||||
{
|
||||
m_matchGroup = _group;
|
||||
m_matchGroups = &_matchGroups;
|
||||
}
|
||||
|
||||
bool Pattern::matches(Expression const& _expr, ExpressionClasses const& _classes) const
|
||||
{
|
||||
if (!matchesBaseItem(_expr.item))
|
||||
return false;
|
||||
if (m_matchGroup)
|
||||
{
|
||||
if (!m_matchGroups->count(m_matchGroup))
|
||||
(*m_matchGroups)[m_matchGroup] = &_expr;
|
||||
else if ((*m_matchGroups)[m_matchGroup]->id != _expr.id)
|
||||
return false;
|
||||
}
|
||||
assertThrow(m_arguments.size() == 0 || _expr.arguments.size() == m_arguments.size(), OptimizerException, "");
|
||||
for (size_t i = 0; i < m_arguments.size(); ++i)
|
||||
if (!m_arguments[i].matches(_classes.representative(_expr.arguments[i]), _classes))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
AssemblyItem Pattern::toAssemblyItem(SourceLocation const& _location) const
|
||||
{
|
||||
if (m_type == Operation)
|
||||
return AssemblyItem(m_instruction, _location);
|
||||
else
|
||||
return AssemblyItem(m_type, data(), _location);
|
||||
}
|
||||
|
||||
string Pattern::toString() const
|
||||
{
|
||||
stringstream s;
|
||||
switch (m_type)
|
||||
{
|
||||
case Operation:
|
||||
s << instructionInfo(m_instruction).name;
|
||||
break;
|
||||
case Push:
|
||||
if (m_data)
|
||||
s << "PUSH " << hex << data();
|
||||
else
|
||||
s << "PUSH ";
|
||||
break;
|
||||
case UndefinedItem:
|
||||
s << "ANY";
|
||||
break;
|
||||
default:
|
||||
if (m_data)
|
||||
s << "t=" << dec << m_type << " d=" << hex << data();
|
||||
else
|
||||
s << "t=" << dec << m_type << " d: nullptr";
|
||||
break;
|
||||
}
|
||||
if (!m_requireDataMatch)
|
||||
s << " ~";
|
||||
if (m_matchGroup)
|
||||
s << "[" << dec << m_matchGroup << "]";
|
||||
s << "(";
|
||||
for (Pattern const& p: m_arguments)
|
||||
s << p.toString() << ", ";
|
||||
s << ")";
|
||||
return s.str();
|
||||
}
|
||||
|
||||
bool Pattern::matchesBaseItem(AssemblyItem const* _item) const
|
||||
{
|
||||
if (m_type == UndefinedItem)
|
||||
return true;
|
||||
if (!_item)
|
||||
return false;
|
||||
if (m_type != _item->type())
|
||||
return false;
|
||||
else if (m_type == Operation)
|
||||
return m_instruction == _item->instruction();
|
||||
else if (m_requireDataMatch)
|
||||
return data() == _item->data();
|
||||
return true;
|
||||
}
|
||||
|
||||
Pattern::Expression const& Pattern::matchGroupValue() const
|
||||
{
|
||||
assertThrow(m_matchGroup > 0, OptimizerException, "");
|
||||
assertThrow(!!m_matchGroups, OptimizerException, "");
|
||||
assertThrow((*m_matchGroups)[m_matchGroup], OptimizerException, "");
|
||||
return *(*m_matchGroups)[m_matchGroup];
|
||||
}
|
||||
|
||||
u256 const& Pattern::data() const
|
||||
{
|
||||
assertThrow(m_data, OptimizerException, "");
|
||||
return *m_data;
|
||||
}
|
||||
|
||||
ExpressionTemplate::ExpressionTemplate(Pattern const& _pattern, SourceLocation const& _location)
|
||||
{
|
||||
if (_pattern.matchGroup())
|
||||
{
|
||||
hasId = true;
|
||||
id = _pattern.id();
|
||||
}
|
||||
else
|
||||
{
|
||||
hasId = false;
|
||||
item = _pattern.toAssemblyItem(_location);
|
||||
}
|
||||
for (auto const& arg: _pattern.arguments())
|
||||
arguments.push_back(ExpressionTemplate(arg, _location));
|
||||
}
|
||||
|
||||
string ExpressionTemplate::toString() const
|
||||
{
|
||||
stringstream s;
|
||||
if (hasId)
|
||||
s << id;
|
||||
else
|
||||
s << item;
|
||||
s << "(";
|
||||
for (auto const& arg: arguments)
|
||||
s << arg.toString();
|
||||
s << ")";
|
||||
return s.str();
|
||||
}
|
||||
140
libevmasm/SimplificationRules.h
Normal file
140
libevmasm/SimplificationRules.h
Normal file
@ -0,0 +1,140 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/**
|
||||
* @file SimplificationRules
|
||||
* @author Christian <chris@ethereum.org>
|
||||
* @date 2017
|
||||
* Module for applying replacement rules against Expressions.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libevmasm/ExpressionClasses.h>
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace eth
|
||||
{
|
||||
|
||||
class Pattern;
|
||||
|
||||
/**
|
||||
* Container for all simplification rules.
|
||||
*/
|
||||
class Rules: public boost::noncopyable
|
||||
{
|
||||
public:
|
||||
using Expression = ExpressionClasses::Expression;
|
||||
|
||||
Rules();
|
||||
|
||||
/// @returns a pointer to the first matching pattern and sets the match
|
||||
/// groups accordingly.
|
||||
std::pair<Pattern, std::function<Pattern()>> const* findFirstMatch(
|
||||
Expression const& _expr,
|
||||
ExpressionClasses const& _classes
|
||||
);
|
||||
|
||||
private:
|
||||
void addRules(std::vector<std::pair<Pattern, std::function<Pattern()>>> const& _rules);
|
||||
void addRule(std::pair<Pattern, std::function<Pattern()>> const& _rule);
|
||||
|
||||
void resetMatchGroups() { m_matchGroups.clear(); }
|
||||
|
||||
std::map<unsigned, Expression const*> m_matchGroups;
|
||||
std::vector<std::pair<Pattern, std::function<Pattern()>>> m_rules[256];
|
||||
};
|
||||
|
||||
/**
|
||||
* Pattern to match against an expression.
|
||||
* Also stores matched expressions to retrieve them later, for constructing new expressions using
|
||||
* ExpressionTemplate.
|
||||
*/
|
||||
class Pattern
|
||||
{
|
||||
public:
|
||||
using Expression = ExpressionClasses::Expression;
|
||||
using Id = ExpressionClasses::Id;
|
||||
|
||||
// Matches a specific constant value.
|
||||
Pattern(unsigned _value): Pattern(u256(_value)) {}
|
||||
// Matches a specific constant value.
|
||||
Pattern(u256 const& _value): m_type(Push), m_requireDataMatch(true), m_data(std::make_shared<u256>(_value)) {}
|
||||
// Matches a specific assembly item type or anything if not given.
|
||||
Pattern(AssemblyItemType _type = UndefinedItem): m_type(_type) {}
|
||||
// Matches a given instruction with given arguments
|
||||
Pattern(Instruction _instruction, std::vector<Pattern> const& _arguments = {});
|
||||
/// Sets this pattern to be part of the match group with the identifier @a _group.
|
||||
/// Inside one rule, all patterns in the same match group have to match expressions from the
|
||||
/// same expression equivalence class.
|
||||
void setMatchGroup(unsigned _group, std::map<unsigned, Expression const*>& _matchGroups);
|
||||
unsigned matchGroup() const { return m_matchGroup; }
|
||||
bool matches(Expression const& _expr, ExpressionClasses const& _classes) const;
|
||||
|
||||
AssemblyItem toAssemblyItem(SourceLocation const& _location) const;
|
||||
std::vector<Pattern> arguments() const { return m_arguments; }
|
||||
|
||||
/// @returns the id of the matched expression if this pattern is part of a match group.
|
||||
Id id() const { return matchGroupValue().id; }
|
||||
/// @returns the data of the matched expression if this pattern is part of a match group.
|
||||
u256 const& d() const { return matchGroupValue().item->data(); }
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
AssemblyItemType type() const { return m_type; }
|
||||
Instruction instruction() const
|
||||
{
|
||||
assertThrow(type() == Operation, OptimizerException, "");
|
||||
return m_instruction;
|
||||
}
|
||||
|
||||
private:
|
||||
bool matchesBaseItem(AssemblyItem const* _item) const;
|
||||
Expression const& matchGroupValue() const;
|
||||
u256 const& data() const;
|
||||
|
||||
AssemblyItemType m_type;
|
||||
bool m_requireDataMatch = false;
|
||||
Instruction m_instruction; ///< Only valid if m_type is Operation
|
||||
std::shared_ptr<u256> m_data; ///< Only valid if m_type is not Operation
|
||||
std::vector<Pattern> m_arguments;
|
||||
unsigned m_matchGroup = 0;
|
||||
std::map<unsigned, Expression const*>* m_matchGroups = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Template for a new expression that can be built from matched patterns.
|
||||
*/
|
||||
struct ExpressionTemplate
|
||||
{
|
||||
using Expression = ExpressionClasses::Expression;
|
||||
using Id = ExpressionClasses::Id;
|
||||
explicit ExpressionTemplate(Pattern const& _pattern, SourceLocation const& _location);
|
||||
std::string toString() const;
|
||||
bool hasId = false;
|
||||
/// Id of the matched expression, if available.
|
||||
Id id = Id(-1);
|
||||
// Otherwise, assembly item.
|
||||
AssemblyItem item = UndefinedItem;
|
||||
std::vector<ExpressionTemplate> arguments;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file CodeFragment.cpp
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file CodeFragment.h
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file Compiler.cpp
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file Compiler.h
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file CompilerState.cpp
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file CompilerState.h
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file Exceptions.h
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file Parser.cpp
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/*
|
||||
This file is part of cpp-ethereum.
|
||||
This file is part of solidity.
|
||||
|
||||
cpp-ethereum is free software: you can redistribute it and/or modify
|
||||
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.
|
||||
|
||||
cpp-ethereum is distributed in the hope that it will be useful,
|
||||
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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/** @file Parser.h
|
||||
* @author Gav Wood <i@gavwood.com>
|
||||
|
||||
@ -42,12 +42,33 @@ Declaration const* DeclarationContainer::conflictingDeclaration(
|
||||
if (m_invisibleDeclarations.count(*_name))
|
||||
declarations += m_invisibleDeclarations.at(*_name);
|
||||
|
||||
if (dynamic_cast<FunctionDefinition const*>(&_declaration))
|
||||
if (
|
||||
dynamic_cast<FunctionDefinition const*>(&_declaration) ||
|
||||
dynamic_cast<EventDefinition const*>(&_declaration)
|
||||
)
|
||||
{
|
||||
// check that all other declarations with the same name are functions
|
||||
// check that all other declarations with the same name are functions or a public state variable or events.
|
||||
// And then check that the signatures are different.
|
||||
for (Declaration const* declaration: declarations)
|
||||
if (!dynamic_cast<FunctionDefinition const*>(declaration))
|
||||
{
|
||||
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(declaration))
|
||||
{
|
||||
if (variableDeclaration->isStateVariable() && !variableDeclaration->isConstant() && variableDeclaration->isPublic())
|
||||
continue;
|
||||
return declaration;
|
||||
}
|
||||
if (
|
||||
dynamic_cast<FunctionDefinition const*>(&_declaration) &&
|
||||
!dynamic_cast<FunctionDefinition const*>(declaration)
|
||||
)
|
||||
return declaration;
|
||||
if (
|
||||
dynamic_cast<EventDefinition const*>(&_declaration) &&
|
||||
!dynamic_cast<EventDefinition const*>(declaration)
|
||||
)
|
||||
return declaration;
|
||||
// Or, continue.
|
||||
}
|
||||
}
|
||||
else if (declarations.size() == 1 && declarations.front() == &_declaration)
|
||||
return nullptr;
|
||||
|
||||
@ -65,7 +65,13 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared<
|
||||
make_shared<MagicVariableDeclaration>("ecrecover",
|
||||
make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Location::ECRecover)),
|
||||
make_shared<MagicVariableDeclaration>("ripemd160",
|
||||
make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true))})
|
||||
make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true)),
|
||||
make_shared<MagicVariableDeclaration>("assert",
|
||||
make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Location::Assert)),
|
||||
make_shared<MagicVariableDeclaration>("require",
|
||||
make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Location::Require)),
|
||||
make_shared<MagicVariableDeclaration>("revert",
|
||||
make_shared<FunctionType>(strings(), strings(), FunctionType::Location::Revert))})
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
#include <libsolidity/analysis/NameAndTypeResolver.h>
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/analysis/TypeChecker.h>
|
||||
#include <libsolidity/interface/Exceptions.h>
|
||||
@ -34,8 +35,10 @@ namespace solidity
|
||||
|
||||
NameAndTypeResolver::NameAndTypeResolver(
|
||||
vector<Declaration const*> const& _globals,
|
||||
map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes,
|
||||
ErrorList& _errors
|
||||
) :
|
||||
m_scopes(_scopes),
|
||||
m_errors(_errors)
|
||||
{
|
||||
if (!m_scopes[nullptr])
|
||||
@ -44,18 +47,12 @@ NameAndTypeResolver::NameAndTypeResolver(
|
||||
m_scopes[nullptr]->registerDeclaration(*declaration);
|
||||
}
|
||||
|
||||
bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit)
|
||||
bool NameAndTypeResolver::registerDeclarations(ASTNode& _sourceUnit, ASTNode const* _currentScope)
|
||||
{
|
||||
if (!m_scopes[&_sourceUnit])
|
||||
// By importing, it is possible that the container already exists.
|
||||
m_scopes[&_sourceUnit].reset(new DeclarationContainer(nullptr, m_scopes[nullptr].get()));
|
||||
m_currentScope = m_scopes[&_sourceUnit].get();
|
||||
|
||||
// The helper registers all declarations in m_scopes as a side-effect of its construction.
|
||||
try
|
||||
{
|
||||
DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errors);
|
||||
_sourceUnit.annotation().exportedSymbols = m_scopes[&_sourceUnit]->declarations();
|
||||
DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errors, _currentScope);
|
||||
}
|
||||
catch (FatalError const&)
|
||||
{
|
||||
@ -132,68 +129,11 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
|
||||
return !error;
|
||||
}
|
||||
|
||||
bool NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract)
|
||||
bool NameAndTypeResolver::resolveNamesAndTypes(ASTNode& _node, bool _resolveInsideCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_currentScope = m_scopes[_contract.scope()].get();
|
||||
solAssert(!!m_currentScope, "");
|
||||
|
||||
ReferencesResolver resolver(m_errors, *this, nullptr);
|
||||
bool success = true;
|
||||
for (ASTPointer<InheritanceSpecifier> const& baseContract: _contract.baseContracts())
|
||||
if (!resolver.resolve(*baseContract))
|
||||
success = false;
|
||||
|
||||
m_currentScope = m_scopes[&_contract].get();
|
||||
|
||||
if (success)
|
||||
{
|
||||
linearizeBaseContracts(_contract);
|
||||
vector<ContractDefinition const*> properBases(
|
||||
++_contract.annotation().linearizedBaseContracts.begin(),
|
||||
_contract.annotation().linearizedBaseContracts.end()
|
||||
);
|
||||
|
||||
for (ContractDefinition const* base: properBases)
|
||||
importInheritedScope(*base);
|
||||
}
|
||||
|
||||
// these can contain code, only resolve parameters for now
|
||||
for (ASTPointer<ASTNode> const& node: _contract.subNodes())
|
||||
{
|
||||
m_currentScope = m_scopes[m_scopes.count(node.get()) ? node.get() : &_contract].get();
|
||||
if (!resolver.resolve(*node))
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
m_currentScope = m_scopes[&_contract].get();
|
||||
|
||||
// now resolve references inside the code
|
||||
for (ModifierDefinition const* modifier: _contract.functionModifiers())
|
||||
{
|
||||
m_currentScope = m_scopes[modifier].get();
|
||||
ReferencesResolver resolver(m_errors, *this, nullptr, true);
|
||||
if (!resolver.resolve(*modifier))
|
||||
success = false;
|
||||
}
|
||||
|
||||
for (FunctionDefinition const* function: _contract.definedFunctions())
|
||||
{
|
||||
m_currentScope = m_scopes[function].get();
|
||||
if (!ReferencesResolver(
|
||||
m_errors,
|
||||
*this,
|
||||
function->returnParameterList().get(),
|
||||
true
|
||||
).resolve(*function))
|
||||
success = false;
|
||||
}
|
||||
if (!success)
|
||||
return false;
|
||||
return resolveNamesAndTypesInternal(_node, _resolveInsideCode);
|
||||
}
|
||||
catch (FatalError const&)
|
||||
{
|
||||
@ -201,7 +141,6 @@ bool NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract)
|
||||
throw; // Something is weird here, rather throw again.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration)
|
||||
@ -257,30 +196,104 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
|
||||
solAssert(_declarations.size() > 1, "");
|
||||
vector<Declaration const*> uniqueFunctions;
|
||||
|
||||
for (auto it = _declarations.begin(); it != _declarations.end(); ++it)
|
||||
for (Declaration const* declaration: _declarations)
|
||||
{
|
||||
solAssert(*it, "");
|
||||
// the declaration is functionDefinition while declarations > 1
|
||||
FunctionDefinition const& functionDefinition = dynamic_cast<FunctionDefinition const&>(**it);
|
||||
FunctionType functionType(functionDefinition);
|
||||
for (auto parameter: functionType.parameterTypes() + functionType.returnParameterTypes())
|
||||
solAssert(declaration, "");
|
||||
// the declaration is functionDefinition, eventDefinition or a VariableDeclaration while declarations > 1
|
||||
solAssert(
|
||||
dynamic_cast<FunctionDefinition const*>(declaration) ||
|
||||
dynamic_cast<EventDefinition const*>(declaration) ||
|
||||
dynamic_cast<VariableDeclaration const*>(declaration),
|
||||
"Found overloading involving something not a function or a variable."
|
||||
);
|
||||
|
||||
FunctionTypePointer functionType { declaration->functionType(false) };
|
||||
if (!functionType)
|
||||
functionType = declaration->functionType(true);
|
||||
solAssert(functionType, "Failed to determine the function type of the overloaded.");
|
||||
|
||||
for (auto parameter: functionType->parameterTypes() + functionType->returnParameterTypes())
|
||||
if (!parameter)
|
||||
reportFatalDeclarationError(_identifier.location(), "Function type can not be used in this context");
|
||||
reportFatalDeclarationError(_identifier.location(), "Function type can not be used in this context.");
|
||||
|
||||
if (uniqueFunctions.end() == find_if(
|
||||
uniqueFunctions.begin(),
|
||||
uniqueFunctions.end(),
|
||||
[&](Declaration const* d)
|
||||
{
|
||||
FunctionType newFunctionType(dynamic_cast<FunctionDefinition const&>(*d));
|
||||
return functionType.hasEqualArgumentTypes(newFunctionType);
|
||||
shared_ptr<FunctionType const> newFunctionType { d->functionType(false) };
|
||||
if (!newFunctionType)
|
||||
newFunctionType = d->functionType(true);
|
||||
return newFunctionType && functionType->hasEqualArgumentTypes(*newFunctionType);
|
||||
}
|
||||
))
|
||||
uniqueFunctions.push_back(*it);
|
||||
uniqueFunctions.push_back(declaration);
|
||||
}
|
||||
return uniqueFunctions;
|
||||
}
|
||||
|
||||
bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode)
|
||||
{
|
||||
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(&_node))
|
||||
{
|
||||
bool success = true;
|
||||
m_currentScope = m_scopes[contract->scope()].get();
|
||||
solAssert(!!m_currentScope, "");
|
||||
|
||||
for (ASTPointer<InheritanceSpecifier> const& baseContract: contract->baseContracts())
|
||||
if (!resolveNamesAndTypes(*baseContract, true))
|
||||
success = false;
|
||||
|
||||
m_currentScope = m_scopes[contract].get();
|
||||
|
||||
if (success)
|
||||
{
|
||||
linearizeBaseContracts(*contract);
|
||||
vector<ContractDefinition const*> properBases(
|
||||
++contract->annotation().linearizedBaseContracts.begin(),
|
||||
contract->annotation().linearizedBaseContracts.end()
|
||||
);
|
||||
|
||||
for (ContractDefinition const* base: properBases)
|
||||
importInheritedScope(*base);
|
||||
}
|
||||
|
||||
// these can contain code, only resolve parameters for now
|
||||
for (ASTPointer<ASTNode> const& node: contract->subNodes())
|
||||
{
|
||||
m_currentScope = m_scopes[contract].get();
|
||||
if (!resolveNamesAndTypes(*node, false))
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
if (!_resolveInsideCode)
|
||||
return success;
|
||||
|
||||
m_currentScope = m_scopes[contract].get();
|
||||
|
||||
// now resolve references inside the code
|
||||
for (ASTPointer<ASTNode> const& node: contract->subNodes())
|
||||
{
|
||||
m_currentScope = m_scopes[contract].get();
|
||||
if (!resolveNamesAndTypes(*node, true))
|
||||
success = false;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_scopes.count(&_node))
|
||||
m_currentScope = m_scopes[&_node].get();
|
||||
return ReferencesResolver(m_errors, *this, _resolveInsideCode).resolve(_node);
|
||||
}
|
||||
}
|
||||
|
||||
void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base)
|
||||
{
|
||||
auto iterator = m_scopes.find(&_base);
|
||||
@ -289,7 +302,39 @@ void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base)
|
||||
for (auto const& declaration: nameAndDeclaration.second)
|
||||
// Import if it was declared in the base, is not the constructor and is visible in derived classes
|
||||
if (declaration->scope() == &_base && declaration->isVisibleInDerivedContracts())
|
||||
m_currentScope->registerDeclaration(*declaration);
|
||||
if (!m_currentScope->registerDeclaration(*declaration))
|
||||
{
|
||||
SourceLocation firstDeclarationLocation;
|
||||
SourceLocation secondDeclarationLocation;
|
||||
Declaration const* conflictingDeclaration = m_currentScope->conflictingDeclaration(*declaration);
|
||||
solAssert(conflictingDeclaration, "");
|
||||
|
||||
// Usual shadowing is not an error
|
||||
if (dynamic_cast<VariableDeclaration const*>(declaration) && dynamic_cast<VariableDeclaration const*>(conflictingDeclaration))
|
||||
continue;
|
||||
|
||||
// Usual shadowing is not an error
|
||||
if (dynamic_cast<ModifierDefinition const*>(declaration) && dynamic_cast<ModifierDefinition const*>(conflictingDeclaration))
|
||||
continue;
|
||||
|
||||
if (declaration->location().start < conflictingDeclaration->location().start)
|
||||
{
|
||||
firstDeclarationLocation = declaration->location();
|
||||
secondDeclarationLocation = conflictingDeclaration->location();
|
||||
}
|
||||
else
|
||||
{
|
||||
firstDeclarationLocation = conflictingDeclaration->location();
|
||||
secondDeclarationLocation = declaration->location();
|
||||
}
|
||||
|
||||
reportDeclarationError(
|
||||
secondDeclarationLocation,
|
||||
"Identifier already declared.",
|
||||
firstDeclarationLocation,
|
||||
"The previous declaration is here:"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract)
|
||||
@ -419,14 +464,30 @@ void NameAndTypeResolver::reportFatalTypeError(Error const& _e)
|
||||
DeclarationRegistrationHelper::DeclarationRegistrationHelper(
|
||||
map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes,
|
||||
ASTNode& _astRoot,
|
||||
ErrorList& _errors
|
||||
ErrorList& _errors,
|
||||
ASTNode const* _currentScope
|
||||
):
|
||||
m_scopes(_scopes),
|
||||
m_currentScope(&_astRoot),
|
||||
m_currentScope(_currentScope),
|
||||
m_errors(_errors)
|
||||
{
|
||||
solAssert(!!m_scopes.at(m_currentScope), "");
|
||||
_astRoot.accept(*this);
|
||||
solAssert(m_currentScope == _currentScope, "Scopes not correctly closed.");
|
||||
}
|
||||
|
||||
bool DeclarationRegistrationHelper::visit(SourceUnit& _sourceUnit)
|
||||
{
|
||||
if (!m_scopes[&_sourceUnit])
|
||||
// By importing, it is possible that the container already exists.
|
||||
m_scopes[&_sourceUnit].reset(new DeclarationContainer(m_currentScope, m_scopes[m_currentScope].get()));
|
||||
m_currentScope = &_sourceUnit;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::endVisit(SourceUnit& _sourceUnit)
|
||||
{
|
||||
_sourceUnit.annotation().exportedSymbols = m_scopes[&_sourceUnit]->declarations();
|
||||
closeCurrentScope();
|
||||
}
|
||||
|
||||
bool DeclarationRegistrationHelper::visit(ImportDirective& _import)
|
||||
@ -547,12 +608,13 @@ void DeclarationRegistrationHelper::enterNewSubScope(Declaration const& _declara
|
||||
|
||||
void DeclarationRegistrationHelper::closeCurrentScope()
|
||||
{
|
||||
solAssert(m_currentScope, "Closed non-existing scope.");
|
||||
solAssert(m_currentScope && m_scopes.count(m_currentScope), "Closed non-existing scope.");
|
||||
m_currentScope = m_scopes[m_currentScope]->enclosingNode();
|
||||
}
|
||||
|
||||
void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration, bool _opensScope)
|
||||
{
|
||||
solAssert(m_currentScope && m_scopes.count(m_currentScope), "No current scope.");
|
||||
if (!m_scopes[m_currentScope]->registerDeclaration(_declaration, nullptr, !_declaration.isVisibleInContract()))
|
||||
{
|
||||
SourceLocation firstDeclarationLocation;
|
||||
|
||||
@ -42,15 +42,27 @@ namespace solidity
|
||||
class NameAndTypeResolver: private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
NameAndTypeResolver(std::vector<Declaration const*> const& _globals, ErrorList& _errors);
|
||||
/// Registers all declarations found in the source unit.
|
||||
/// Creates the resolver with the given declarations added to the global scope.
|
||||
/// @param _scopes mapping of scopes to be used (usually default constructed), these
|
||||
/// are filled during the lifetime of this object.
|
||||
NameAndTypeResolver(
|
||||
std::vector<Declaration const*> const& _globals,
|
||||
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes,
|
||||
ErrorList& _errors
|
||||
);
|
||||
/// Registers all declarations found in the AST node, usually a source unit.
|
||||
/// @returns false in case of error.
|
||||
bool registerDeclarations(SourceUnit& _sourceUnit);
|
||||
/// @param _currentScope should be nullptr but can be used to inject new declarations into
|
||||
/// existing scopes, used by the snippets feature.
|
||||
bool registerDeclarations(ASTNode& _sourceUnit, ASTNode const* _currentScope = nullptr);
|
||||
/// Applies the effect of import directives.
|
||||
bool performImports(SourceUnit& _sourceUnit, std::map<std::string, SourceUnit const*> const& _sourceUnits);
|
||||
/// Resolves all names and types referenced from the given contract.
|
||||
/// Resolves all names and types referenced from the given AST Node.
|
||||
/// This is usually only called at the contract level, but with a bit of care, it can also
|
||||
/// be called at deeper levels.
|
||||
/// @param _resolveInsideCode if false, does not descend into nodes that contain code.
|
||||
/// @returns false in case of error.
|
||||
bool resolveNamesAndTypes(ContractDefinition& _contract);
|
||||
bool resolveNamesAndTypes(ASTNode& _node, bool _resolveInsideCode = true);
|
||||
/// Updates the given global declaration (used for "this"). Not to be used with declarations
|
||||
/// that create their own scope.
|
||||
/// @returns false in case of error.
|
||||
@ -77,7 +89,8 @@ public:
|
||||
);
|
||||
|
||||
private:
|
||||
void reset();
|
||||
/// Internal version of @a resolveNamesAndTypes (called from there) throws exceptions on fatal errors.
|
||||
bool resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode = true);
|
||||
|
||||
/// Imports all members declared directly in the given contract (i.e. does not import inherited members)
|
||||
/// into the current scope if they are not present already.
|
||||
@ -112,7 +125,7 @@ private:
|
||||
/// where nullptr denotes the global scope. Note that structs are not scope since they do
|
||||
/// not contain code.
|
||||
/// Aliases (for example `import "x" as y;`) create multiple pointers to the same scope.
|
||||
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>> m_scopes;
|
||||
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& m_scopes;
|
||||
|
||||
DeclarationContainer* m_currentScope = nullptr;
|
||||
ErrorList& m_errors;
|
||||
@ -125,13 +138,20 @@ private:
|
||||
class DeclarationRegistrationHelper: private ASTVisitor
|
||||
{
|
||||
public:
|
||||
/// Registers declarations in their scopes and creates new scopes as a side-effect
|
||||
/// of construction.
|
||||
/// @param _currentScope should be nullptr if we start at SourceUnit, but can be different
|
||||
/// to inject new declarations into an existing scope, used by snippets.
|
||||
DeclarationRegistrationHelper(
|
||||
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes,
|
||||
ASTNode& _astRoot,
|
||||
ErrorList& _errors
|
||||
ErrorList& _errors,
|
||||
ASTNode const* _currentScope = nullptr
|
||||
);
|
||||
|
||||
private:
|
||||
bool visit(SourceUnit& _sourceUnit) override;
|
||||
void endVisit(SourceUnit& _sourceUnit) override;
|
||||
bool visit(ImportDirective& _declaration) override;
|
||||
bool visit(ContractDefinition& _contract) override;
|
||||
void endVisit(ContractDefinition& _contract) override;
|
||||
|
||||
108
libsolidity/analysis/PostTypeChecker.cpp
Normal file
108
libsolidity/analysis/PostTypeChecker.cpp
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <libsolidity/analysis/PostTypeChecker.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/analysis/SemVerHandler.h>
|
||||
#include <libsolidity/interface/Version.h>
|
||||
|
||||
#include <boost/range/adaptor/map.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
|
||||
|
||||
bool PostTypeChecker::check(ASTNode const& _astRoot)
|
||||
{
|
||||
_astRoot.accept(*this);
|
||||
return Error::containsOnlyWarnings(m_errors);
|
||||
}
|
||||
|
||||
void PostTypeChecker::typeError(SourceLocation const& _location, std::string const& _description)
|
||||
{
|
||||
auto err = make_shared<Error>(Error::Type::TypeError);
|
||||
*err <<
|
||||
errinfo_sourceLocation(_location) <<
|
||||
errinfo_comment(_description);
|
||||
|
||||
m_errors.push_back(err);
|
||||
}
|
||||
|
||||
bool PostTypeChecker::visit(ContractDefinition const&)
|
||||
{
|
||||
solAssert(!m_currentConstVariable, "");
|
||||
solAssert(m_constVariableDependencies.empty(), "");
|
||||
return true;
|
||||
}
|
||||
|
||||
void PostTypeChecker::endVisit(ContractDefinition const&)
|
||||
{
|
||||
solAssert(!m_currentConstVariable, "");
|
||||
for (auto declaration: m_constVariables)
|
||||
if (auto identifier = findCycle(declaration))
|
||||
typeError(declaration->location(), "The value of the constant " + declaration->name() + " has a cyclic dependency via " + identifier->name() + ".");
|
||||
}
|
||||
|
||||
bool PostTypeChecker::visit(VariableDeclaration const& _variable)
|
||||
{
|
||||
solAssert(!m_currentConstVariable, "");
|
||||
if (_variable.isConstant())
|
||||
{
|
||||
m_currentConstVariable = &_variable;
|
||||
m_constVariables.push_back(&_variable);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PostTypeChecker::endVisit(VariableDeclaration const& _variable)
|
||||
{
|
||||
if (_variable.isConstant())
|
||||
{
|
||||
solAssert(m_currentConstVariable == &_variable, "");
|
||||
m_currentConstVariable = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool PostTypeChecker::visit(Identifier const& _identifier)
|
||||
{
|
||||
if (m_currentConstVariable)
|
||||
if (auto var = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration))
|
||||
if (var->isConstant())
|
||||
m_constVariableDependencies[m_currentConstVariable].insert(var);
|
||||
return true;
|
||||
}
|
||||
|
||||
VariableDeclaration const* PostTypeChecker::findCycle(
|
||||
VariableDeclaration const* _startingFrom,
|
||||
set<VariableDeclaration const*> const& _seen
|
||||
)
|
||||
{
|
||||
if (_seen.count(_startingFrom))
|
||||
return _startingFrom;
|
||||
else if (m_constVariableDependencies.count(_startingFrom))
|
||||
{
|
||||
set<VariableDeclaration const*> seen(_seen);
|
||||
seen.insert(_startingFrom);
|
||||
for (auto v: m_constVariableDependencies[_startingFrom])
|
||||
if (findCycle(v, seen))
|
||||
return v;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
69
libsolidity/analysis/PostTypeChecker.h
Normal file
69
libsolidity/analysis/PostTypeChecker.h
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
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 <libsolidity/analysis/TypeChecker.h>
|
||||
#include <libsolidity/ast/Types.h>
|
||||
#include <libsolidity/ast/ASTAnnotations.h>
|
||||
#include <libsolidity/ast/ASTForward.h>
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
/**
|
||||
* This module performs analyses on the AST that are done after type checking and assignments of types:
|
||||
* - whether there are circular references in constant state variables
|
||||
* @TODO factor out each use-case into an individual class (but do the traversal only once)
|
||||
*/
|
||||
class PostTypeChecker: private ASTConstVisitor
|
||||
{
|
||||
public:
|
||||
/// @param _errors the reference to the list of errors and warnings to add them found during type checking.
|
||||
PostTypeChecker(ErrorList& _errors): m_errors(_errors) {}
|
||||
|
||||
bool check(ASTNode const& _astRoot);
|
||||
|
||||
private:
|
||||
/// Adds a new error to the list of errors.
|
||||
void typeError(SourceLocation const& _location, std::string const& _description);
|
||||
|
||||
virtual bool visit(ContractDefinition const& _contract) override;
|
||||
virtual void endVisit(ContractDefinition const& _contract) override;
|
||||
|
||||
virtual bool visit(VariableDeclaration const& _declaration) override;
|
||||
virtual void endVisit(VariableDeclaration const& _declaration) override;
|
||||
|
||||
virtual bool visit(Identifier const& _identifier) override;
|
||||
|
||||
VariableDeclaration const* findCycle(
|
||||
VariableDeclaration const* _startingFrom,
|
||||
std::set<VariableDeclaration const*> const& _seen = {}
|
||||
);
|
||||
|
||||
ErrorList& m_errors;
|
||||
|
||||
VariableDeclaration const* m_currentConstVariable = nullptr;
|
||||
std::vector<VariableDeclaration const*> m_constVariables; ///< Required for determinism.
|
||||
std::map<VariableDeclaration const*, std::set<VariableDeclaration const*>> m_constVariableDependencies;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
@ -35,14 +35,7 @@ using namespace dev::solidity;
|
||||
|
||||
bool ReferencesResolver::resolve(ASTNode const& _root)
|
||||
{
|
||||
try
|
||||
{
|
||||
_root.accept(*this);
|
||||
}
|
||||
catch (FatalError const&)
|
||||
{
|
||||
solAssert(m_errorOccurred, "");
|
||||
}
|
||||
_root.accept(*this);
|
||||
return !m_errorOccurred;
|
||||
}
|
||||
|
||||
@ -65,6 +58,30 @@ bool ReferencesResolver::visit(ElementaryTypeName const& _typeName)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReferencesResolver::visit(FunctionDefinition const& _functionDefinition)
|
||||
{
|
||||
m_returnParameters.push_back(_functionDefinition.returnParameterList().get());
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReferencesResolver::endVisit(FunctionDefinition const&)
|
||||
{
|
||||
solAssert(!m_returnParameters.empty(), "");
|
||||
m_returnParameters.pop_back();
|
||||
}
|
||||
|
||||
bool ReferencesResolver::visit(ModifierDefinition const&)
|
||||
{
|
||||
m_returnParameters.push_back(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReferencesResolver::endVisit(ModifierDefinition const&)
|
||||
{
|
||||
solAssert(!m_returnParameters.empty(), "");
|
||||
m_returnParameters.pop_back();
|
||||
}
|
||||
|
||||
void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName)
|
||||
{
|
||||
Declaration const* declaration = m_resolver.pathFromCurrentScope(_typeName.namePath());
|
||||
@ -87,7 +104,6 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
|
||||
{
|
||||
switch (_typeName.visibility())
|
||||
{
|
||||
case VariableDeclaration::Visibility::Default:
|
||||
case VariableDeclaration::Visibility::Internal:
|
||||
case VariableDeclaration::Visibility::External:
|
||||
break;
|
||||
@ -131,6 +147,8 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName)
|
||||
auto const* lengthType = dynamic_cast<RationalNumberType const*>(length->annotation().type.get());
|
||||
if (!lengthType || lengthType->isFractional())
|
||||
fatalTypeError(length->location(), "Invalid array length, expected integer literal.");
|
||||
else if (lengthType->isNegative())
|
||||
fatalTypeError(length->location(), "Array with negative length specified.");
|
||||
else
|
||||
_typeName.annotation().type = make_shared<ArrayType>(DataLocation::Storage, baseType, lengthType->literalValue(nullptr));
|
||||
}
|
||||
@ -160,7 +178,8 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
|
||||
|
||||
bool ReferencesResolver::visit(Return const& _return)
|
||||
{
|
||||
_return.annotation().functionReturnParameters = m_returnParameters;
|
||||
solAssert(!m_returnParameters.empty(), "");
|
||||
_return.annotation().functionReturnParameters = m_returnParameters.back();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -45,22 +45,24 @@ public:
|
||||
ReferencesResolver(
|
||||
ErrorList& _errors,
|
||||
NameAndTypeResolver& _resolver,
|
||||
ParameterList const* _returnParameters,
|
||||
bool _resolveInsideCode = false
|
||||
):
|
||||
m_errors(_errors),
|
||||
m_resolver(_resolver),
|
||||
m_returnParameters(_returnParameters),
|
||||
m_resolveInsideCode(_resolveInsideCode)
|
||||
{}
|
||||
|
||||
/// @returns true if no errors during resolving
|
||||
/// @returns true if no errors during resolving and throws exceptions on fatal errors.
|
||||
bool resolve(ASTNode const& _root);
|
||||
|
||||
private:
|
||||
virtual bool visit(Block const&) override { return m_resolveInsideCode; }
|
||||
virtual bool visit(Identifier const& _identifier) override;
|
||||
virtual bool visit(ElementaryTypeName const& _typeName) override;
|
||||
virtual bool visit(FunctionDefinition const& _functionDefinition) override;
|
||||
virtual void endVisit(FunctionDefinition const& _functionDefinition) override;
|
||||
virtual bool visit(ModifierDefinition const& _modifierDefinition) override;
|
||||
virtual void endVisit(ModifierDefinition const& _modifierDefinition) override;
|
||||
virtual void endVisit(UserDefinedTypeName const& _typeName) override;
|
||||
virtual void endVisit(FunctionTypeName const& _typeName) override;
|
||||
virtual void endVisit(Mapping const& _typeName) override;
|
||||
@ -83,7 +85,8 @@ private:
|
||||
|
||||
ErrorList& m_errors;
|
||||
NameAndTypeResolver& m_resolver;
|
||||
ParameterList const* m_returnParameters;
|
||||
/// Stack of return parameters.
|
||||
std::vector<ParameterList const*> m_returnParameters;
|
||||
bool const m_resolveInsideCode;
|
||||
bool m_errorOccurred = false;
|
||||
};
|
||||
|
||||
@ -34,6 +34,9 @@ class SemVerError: dev::Exception
|
||||
{
|
||||
};
|
||||
|
||||
#undef major
|
||||
#undef minor
|
||||
|
||||
struct SemVerVersion
|
||||
{
|
||||
unsigned numbers[3];
|
||||
|
||||
@ -36,6 +36,9 @@ namespace solidity
|
||||
|
||||
/**
|
||||
* The module that performs static analysis on the AST.
|
||||
* In this context, static analysis is anything that can produce warnings which can help
|
||||
* programmers write cleaner code. For every warning generated eher, it has to be possible to write
|
||||
* equivalent code that does generate the warning.
|
||||
*/
|
||||
class StaticAnalyzer: private ASTConstVisitor
|
||||
{
|
||||
|
||||
@ -26,9 +26,9 @@ using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
|
||||
|
||||
bool SyntaxChecker::checkSyntax(SourceUnit const& _sourceUnit)
|
||||
bool SyntaxChecker::checkSyntax(ASTNode const& _astRoot)
|
||||
{
|
||||
_sourceUnit.accept(*this);
|
||||
_astRoot.accept(*this);
|
||||
return Error::containsOnlyWarnings(m_errors);
|
||||
}
|
||||
|
||||
|
||||
@ -39,7 +39,7 @@ public:
|
||||
/// @param _errors the reference to the list of errors and warnings to add them found during type checking.
|
||||
SyntaxChecker(ErrorList& _errors): m_errors(_errors) {}
|
||||
|
||||
bool checkSyntax(SourceUnit const& _sourceUnit);
|
||||
bool checkSyntax(ASTNode const& _astRoot);
|
||||
|
||||
private:
|
||||
/// Adds a new error to the list of errors.
|
||||
|
||||
@ -32,11 +32,11 @@ using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
|
||||
|
||||
bool TypeChecker::checkTypeRequirements(ContractDefinition const& _contract)
|
||||
bool TypeChecker::checkTypeRequirements(ASTNode const& _contract)
|
||||
{
|
||||
try
|
||||
{
|
||||
visit(_contract);
|
||||
_contract.accept(*this);
|
||||
}
|
||||
catch (FatalError const&)
|
||||
{
|
||||
@ -75,7 +75,8 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
|
||||
checkContractAbstractConstructors(_contract);
|
||||
|
||||
FunctionDefinition const* function = _contract.constructor();
|
||||
if (function) {
|
||||
if (function)
|
||||
{
|
||||
if (!function->returnParameters().empty())
|
||||
typeError(function->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor.");
|
||||
if (function->isDeclaredConst())
|
||||
@ -424,7 +425,9 @@ bool TypeChecker::visit(StructDefinition const& _struct)
|
||||
|
||||
bool TypeChecker::visit(FunctionDefinition const& _function)
|
||||
{
|
||||
bool isLibraryFunction = dynamic_cast<ContractDefinition const&>(*_function.scope()).isLibrary();
|
||||
bool isLibraryFunction =
|
||||
dynamic_cast<ContractDefinition const*>(_function.scope()) &&
|
||||
dynamic_cast<ContractDefinition const*>(_function.scope())->isLibrary();
|
||||
if (_function.isPayable())
|
||||
{
|
||||
if (isLibraryFunction)
|
||||
@ -464,27 +467,29 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
|
||||
// TypeChecker at the VariableDeclarationStatement level.
|
||||
TypePointer varType = _variable.annotation().type;
|
||||
solAssert(!!varType, "Failed to infer variable type.");
|
||||
if (_variable.isConstant())
|
||||
{
|
||||
if (!dynamic_cast<ContractDefinition const*>(_variable.scope()))
|
||||
typeError(_variable.location(), "Illegal use of \"constant\" specifier.");
|
||||
if (!_variable.value())
|
||||
typeError(_variable.location(), "Uninitialized \"constant\" variable.");
|
||||
if (!varType->isValueType())
|
||||
{
|
||||
bool constImplemented = false;
|
||||
if (auto arrayType = dynamic_cast<ArrayType const*>(varType.get()))
|
||||
constImplemented = arrayType->isByteArray();
|
||||
if (!constImplemented)
|
||||
typeError(
|
||||
_variable.location(),
|
||||
"Illegal use of \"constant\" specifier. \"constant\" "
|
||||
"is not yet implemented for this type."
|
||||
);
|
||||
}
|
||||
}
|
||||
if (_variable.value())
|
||||
expectType(*_variable.value(), *varType);
|
||||
if (_variable.isConstant())
|
||||
{
|
||||
if (!_variable.isStateVariable())
|
||||
typeError(_variable.location(), "Illegal use of \"constant\" specifier.");
|
||||
if (!_variable.type()->isValueType())
|
||||
{
|
||||
bool allowed = false;
|
||||
if (auto arrayType = dynamic_cast<ArrayType const*>(_variable.type().get()))
|
||||
allowed = arrayType->isString();
|
||||
if (!allowed)
|
||||
typeError(_variable.location(), "Constants of non-value type not yet implemented.");
|
||||
}
|
||||
if (!_variable.value())
|
||||
typeError(_variable.location(), "Uninitialized \"constant\" variable.");
|
||||
else if (!_variable.value()->annotation().isPure)
|
||||
warning(
|
||||
_variable.value()->location(),
|
||||
"Initial value for constant variable has to be compile-time constant. "
|
||||
"This will fail to compile with the next breaking version change."
|
||||
);
|
||||
}
|
||||
if (!_variable.isStateVariable())
|
||||
{
|
||||
if (varType->dataStoredIn(DataLocation::Memory) || varType->dataStoredIn(DataLocation::CallData))
|
||||
@ -587,8 +592,12 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
// Inline assembly does not have its own type-checking phase, so we just run the
|
||||
// code-generator and see whether it produces any errors.
|
||||
// External references have already been resolved in a prior stage and stored in the annotation.
|
||||
assembly::CodeGenerator codeGen(_inlineAssembly.operations(), m_errors);
|
||||
codeGen.typeCheck([&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) {
|
||||
auto identifierAccess = [&](
|
||||
assembly::Identifier const& _identifier,
|
||||
eth::Assembly& _assembly,
|
||||
assembly::CodeGenerator::IdentifierContext _context
|
||||
)
|
||||
{
|
||||
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
|
||||
if (ref == _inlineAssembly.annotation().externalReferences.end())
|
||||
return false;
|
||||
@ -606,7 +615,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
fatalTypeError(SourceLocation(), "Constant variables not yet implemented for inline assembly.");
|
||||
if (var->isLocalVariable())
|
||||
pushes = var->type()->sizeOnStack();
|
||||
else if (var->type()->isValueType())
|
||||
else if (!var->type()->isValueType())
|
||||
pushes = 1;
|
||||
else
|
||||
pushes = 2; // slot number, intra slot offset
|
||||
@ -636,8 +645,11 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return false;
|
||||
};
|
||||
assembly::CodeGenerator codeGen(_inlineAssembly.operations(), m_errors);
|
||||
if (!codeGen.typeCheck(identifierAccess))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TypeChecker::visit(IfStatement const& _ifStatement)
|
||||
@ -725,13 +737,16 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
|
||||
if (auto ref = dynamic_cast<ReferenceType const*>(type(varDecl).get()))
|
||||
{
|
||||
if (ref->dataStoredIn(DataLocation::Storage))
|
||||
{
|
||||
warning(
|
||||
varDecl.location(),
|
||||
"Uninitialized storage pointer. Did you mean '<type> memory " + varDecl.name() + "'?"
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (dynamic_cast<MappingType const*>(type(varDecl).get()))
|
||||
typeError(
|
||||
varDecl.location(),
|
||||
"Uninitialized mapping. Mappings cannot be created dynamically, you have to assign them from a state variable."
|
||||
);
|
||||
varDecl.accept(*this);
|
||||
return false;
|
||||
}
|
||||
@ -819,6 +834,11 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
|
||||
else
|
||||
solAssert(false, "");
|
||||
}
|
||||
else if (*var.annotation().type == TupleType())
|
||||
typeError(
|
||||
var.location(),
|
||||
"Cannot declare variable with void (empty tuple) type."
|
||||
);
|
||||
var.accept(*this);
|
||||
}
|
||||
else
|
||||
@ -871,10 +891,11 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement)
|
||||
if (
|
||||
location == Location::Bare ||
|
||||
location == Location::BareCallCode ||
|
||||
location == Location::BareDelegateCall ||
|
||||
location == Location::Send
|
||||
location == Location::BareDelegateCall
|
||||
)
|
||||
warning(_statement.location(), "Return value of low-level calls not used.");
|
||||
else if (location == Location::Send)
|
||||
warning(_statement.location(), "Failure condition of 'send' ignored. Consider using 'transfer' instead.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -910,6 +931,10 @@ bool TypeChecker::visit(Conditional const& _conditional)
|
||||
}
|
||||
|
||||
_conditional.annotation().type = commonType;
|
||||
_conditional.annotation().isPure =
|
||||
_conditional.condition().annotation().isPure &&
|
||||
_conditional.trueExpression().annotation().isPure &&
|
||||
_conditional.falseExpression().annotation().isPure;
|
||||
|
||||
if (_conditional.annotation().lValueRequested)
|
||||
typeError(
|
||||
@ -927,6 +952,11 @@ bool TypeChecker::visit(Assignment const& _assignment)
|
||||
_assignment.annotation().type = t;
|
||||
if (TupleType const* tupleType = dynamic_cast<TupleType const*>(t.get()))
|
||||
{
|
||||
if (_assignment.assignmentOperator() != Token::Assign)
|
||||
typeError(
|
||||
_assignment.location(),
|
||||
"Compound assignment is not allowed for tuple types."
|
||||
);
|
||||
// Sequenced assignments of tuples is not valid, make the result a "void" type.
|
||||
_assignment.annotation().type = make_shared<TupleType>();
|
||||
expectType(_assignment.rightHandSide(), *tupleType);
|
||||
@ -986,6 +1016,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isPure = true;
|
||||
TypePointer inlineArrayType;
|
||||
for (size_t i = 0; i < components.size(); ++i)
|
||||
{
|
||||
@ -1004,14 +1035,17 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
|
||||
fatalTypeError(components[i]->location(), "Invalid mobile type.");
|
||||
|
||||
if (i == 0)
|
||||
inlineArrayType = types[i];
|
||||
inlineArrayType = types[i]->mobileType();
|
||||
else if (inlineArrayType)
|
||||
inlineArrayType = Type::commonType(inlineArrayType, types[i]);
|
||||
}
|
||||
if (!components[i]->annotation().isPure)
|
||||
isPure = false;
|
||||
}
|
||||
else
|
||||
types.push_back(TypePointer());
|
||||
}
|
||||
_tuple.annotation().isPure = isPure;
|
||||
if (_tuple.isInlineArray())
|
||||
{
|
||||
if (!inlineArrayType)
|
||||
@ -1038,7 +1072,8 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
|
||||
{
|
||||
// Inc, Dec, Add, Sub, Not, BitNot, Delete
|
||||
Token::Value op = _operation.getOperator();
|
||||
if (op == Token::Value::Inc || op == Token::Value::Dec || op == Token::Value::Delete)
|
||||
bool const modifying = (op == Token::Value::Inc || op == Token::Value::Dec || op == Token::Value::Delete);
|
||||
if (modifying)
|
||||
requireLValue(_operation.subExpression());
|
||||
else
|
||||
_operation.subExpression().accept(*this);
|
||||
@ -1056,6 +1091,7 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
|
||||
t = subExprType;
|
||||
}
|
||||
_operation.annotation().type = t;
|
||||
_operation.annotation().isPure = !modifying && _operation.subExpression().annotation().isPure;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1082,6 +1118,30 @@ void TypeChecker::endVisit(BinaryOperation const& _operation)
|
||||
Token::isCompareOp(_operation.getOperator()) ?
|
||||
make_shared<BoolType>() :
|
||||
commonType;
|
||||
_operation.annotation().isPure =
|
||||
_operation.leftExpression().annotation().isPure &&
|
||||
_operation.rightExpression().annotation().isPure;
|
||||
|
||||
if (_operation.getOperator() == Token::Exp)
|
||||
{
|
||||
if (
|
||||
leftType->category() == Type::Category::RationalNumber &&
|
||||
rightType->category() != Type::Category::RationalNumber
|
||||
)
|
||||
if ((
|
||||
commonType->category() == Type::Category::Integer &&
|
||||
dynamic_cast<IntegerType const&>(*commonType).numBits() != 256
|
||||
) || (
|
||||
commonType->category() == Type::Category::FixedPoint &&
|
||||
dynamic_cast<FixedPointType const&>(*commonType).numBits() != 256
|
||||
))
|
||||
warning(
|
||||
_operation.location(),
|
||||
"Result of exponentiation has type " + commonType->toString() + " and thus "
|
||||
"might overflow. Silence this warning by converting the literal to the "
|
||||
"expected type."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
@ -1090,6 +1150,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
vector<ASTPointer<Expression const>> arguments = _functionCall.arguments();
|
||||
vector<ASTPointer<ASTString>> const& argumentNames = _functionCall.names();
|
||||
|
||||
bool isPure = true;
|
||||
|
||||
// We need to check arguments' type first as they will be needed for overload resolution.
|
||||
shared_ptr<TypePointers> argumentTypes;
|
||||
if (isPositionalCall)
|
||||
@ -1097,6 +1159,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
for (ASTPointer<Expression const> const& argument: arguments)
|
||||
{
|
||||
argument->accept(*this);
|
||||
if (!argument->annotation().isPure)
|
||||
isPure = false;
|
||||
// only store them for positional calls
|
||||
if (isPositionalCall)
|
||||
argumentTypes->push_back(type(*argument));
|
||||
@ -1134,6 +1198,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
typeError(_functionCall.location(), "Explicit type conversion not allowed.");
|
||||
}
|
||||
_functionCall.annotation().type = resultType;
|
||||
_functionCall.annotation().isPure = isPure;
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -1150,9 +1215,16 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
||||
auto const& structType = dynamic_cast<StructType const&>(*t.actualType());
|
||||
functionType = structType.constructorType();
|
||||
membersRemovedForStructConstructor = structType.membersMissingInMemory();
|
||||
_functionCall.annotation().isPure = isPure;
|
||||
}
|
||||
else
|
||||
{
|
||||
functionType = dynamic_pointer_cast<FunctionType const>(expressionType);
|
||||
_functionCall.annotation().isPure =
|
||||
isPure &&
|
||||
_functionCall.expression().annotation().isPure &&
|
||||
functionType->isPure();
|
||||
}
|
||||
|
||||
if (!functionType)
|
||||
{
|
||||
@ -1280,6 +1352,8 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
|
||||
fatalTypeError(_newExpression.location(), "Identifier is not a contract.");
|
||||
if (!contract->annotation().isFullyImplemented)
|
||||
typeError(_newExpression.location(), "Trying to create an instance of an abstract contract.");
|
||||
if (!contract->constructorIsPublic())
|
||||
typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly.");
|
||||
|
||||
solAssert(!!m_scope, "");
|
||||
m_scope->annotation().contractDependencies.insert(contract);
|
||||
@ -1315,6 +1389,7 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
|
||||
strings(),
|
||||
FunctionType::Location::ObjectCreation
|
||||
);
|
||||
_newExpression.annotation().isPure = true;
|
||||
}
|
||||
else
|
||||
fatalTypeError(_newExpression.location(), "Contract or array type expected.");
|
||||
@ -1400,6 +1475,12 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
||||
annotation.isLValue = annotation.referencedDeclaration->isLValue();
|
||||
}
|
||||
|
||||
// TODO some members might be pure, but for example `address(0x123).balance` is not pure
|
||||
// although every subexpression is, so leaving this limited for now.
|
||||
if (auto tt = dynamic_cast<TypeType const*>(exprType.get()))
|
||||
if (tt->actualType()->category() == Type::Category::Enum)
|
||||
annotation.isPure = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1409,6 +1490,7 @@ bool TypeChecker::visit(IndexAccess const& _access)
|
||||
TypePointer baseType = type(_access.baseExpression());
|
||||
TypePointer resultType;
|
||||
bool isLValue = false;
|
||||
bool isPure = _access.baseExpression().annotation().isPure;
|
||||
Expression const* index = _access.indexExpression();
|
||||
switch (baseType->category())
|
||||
{
|
||||
@ -1490,6 +1572,9 @@ bool TypeChecker::visit(IndexAccess const& _access)
|
||||
}
|
||||
_access.annotation().type = move(resultType);
|
||||
_access.annotation().isLValue = isLValue;
|
||||
if (index && !index->annotation().isPure)
|
||||
isPure = false;
|
||||
_access.annotation().isPure = isPure;
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -1500,8 +1585,23 @@ bool TypeChecker::visit(Identifier const& _identifier)
|
||||
if (!annotation.referencedDeclaration)
|
||||
{
|
||||
if (!annotation.argumentTypes)
|
||||
fatalTypeError(_identifier.location(), "Unable to determine overloaded type.");
|
||||
if (annotation.overloadedDeclarations.empty())
|
||||
{
|
||||
// The identifier should be a public state variable shadowing other functions
|
||||
vector<Declaration const*> candidates;
|
||||
|
||||
for (Declaration const* declaration: annotation.overloadedDeclarations)
|
||||
{
|
||||
if (VariableDeclaration const* variableDeclaration = dynamic_cast<decltype(variableDeclaration)>(declaration))
|
||||
candidates.push_back(declaration);
|
||||
}
|
||||
if (candidates.empty())
|
||||
fatalTypeError(_identifier.location(), "No matching declaration found after variable lookup.");
|
||||
else if (candidates.size() == 1)
|
||||
annotation.referencedDeclaration = candidates.front();
|
||||
else
|
||||
fatalTypeError(_identifier.location(), "No unique declaration found after variable lookup.");
|
||||
}
|
||||
else if (annotation.overloadedDeclarations.empty())
|
||||
fatalTypeError(_identifier.location(), "No candidates for overload resolution found.");
|
||||
else if (annotation.overloadedDeclarations.size() == 1)
|
||||
annotation.referencedDeclaration = *annotation.overloadedDeclarations.begin();
|
||||
@ -1529,23 +1629,42 @@ bool TypeChecker::visit(Identifier const& _identifier)
|
||||
!!annotation.referencedDeclaration,
|
||||
"Referenced declaration is null after overload resolution."
|
||||
);
|
||||
auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration);
|
||||
annotation.isConstant = variableDeclaration != nullptr && variableDeclaration->isConstant();
|
||||
annotation.isLValue = annotation.referencedDeclaration->isLValue();
|
||||
annotation.type = annotation.referencedDeclaration->type();
|
||||
if (!annotation.type)
|
||||
fatalTypeError(_identifier.location(), "Declaration referenced before type could be determined.");
|
||||
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration))
|
||||
annotation.isPure = annotation.isConstant = variableDeclaration->isConstant();
|
||||
else if (dynamic_cast<MagicVariableDeclaration const*>(annotation.referencedDeclaration))
|
||||
if (auto functionType = dynamic_cast<FunctionType const*>(annotation.type.get()))
|
||||
annotation.isPure = functionType->isPure();
|
||||
return false;
|
||||
}
|
||||
|
||||
void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr)
|
||||
{
|
||||
_expr.annotation().type = make_shared<TypeType>(Type::fromElementaryTypeName(_expr.typeName()));
|
||||
_expr.annotation().isPure = true;
|
||||
}
|
||||
|
||||
void TypeChecker::endVisit(Literal const& _literal)
|
||||
{
|
||||
if (_literal.looksLikeAddress())
|
||||
{
|
||||
if (_literal.passesAddressChecksum())
|
||||
{
|
||||
_literal.annotation().type = make_shared<IntegerType>(0, IntegerType::Modifier::Address);
|
||||
return;
|
||||
}
|
||||
else
|
||||
warning(
|
||||
_literal.location(),
|
||||
"This looks like an address but has an invalid checksum. "
|
||||
"If this is not used as an address, please prepend '00'."
|
||||
);
|
||||
}
|
||||
_literal.annotation().type = Type::forLiteral(_literal);
|
||||
_literal.annotation().isPure = true;
|
||||
if (!_literal.annotation().type)
|
||||
fatalTypeError(_literal.location(), "Invalid literal value.");
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ public:
|
||||
|
||||
/// Performs type checking on the given contract and all of its sub-nodes.
|
||||
/// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings
|
||||
bool checkTypeRequirements(ContractDefinition const& _contract);
|
||||
bool checkTypeRequirements(ASTNode const& _contract);
|
||||
|
||||
/// @returns the type of an expression and asserts that it is present.
|
||||
TypePointer const& type(Expression const& _expression) const;
|
||||
|
||||
@ -20,8 +20,6 @@
|
||||
* Solidity abstract syntax tree.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <libsolidity/interface/Utils.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
@ -30,11 +28,31 @@
|
||||
|
||||
#include <libdevcore/SHA3.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
|
||||
class IDDispenser
|
||||
{
|
||||
public:
|
||||
static size_t next() { return ++instance(); }
|
||||
static void reset() { instance() = 0; }
|
||||
private:
|
||||
static size_t& instance()
|
||||
{
|
||||
static IDDispenser dispenser;
|
||||
return dispenser.id;
|
||||
}
|
||||
size_t id = 0;
|
||||
};
|
||||
|
||||
ASTNode::ASTNode(SourceLocation const& _location):
|
||||
m_id(IDDispenser::next()),
|
||||
m_location(_location)
|
||||
{
|
||||
}
|
||||
@ -44,6 +62,11 @@ ASTNode::~ASTNode()
|
||||
delete m_annotation;
|
||||
}
|
||||
|
||||
void ASTNode::resetID()
|
||||
{
|
||||
IDDispenser::reset();
|
||||
}
|
||||
|
||||
ASTAnnotation& ASTNode::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
@ -60,7 +83,7 @@ SourceUnitAnnotation& SourceUnit::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new SourceUnitAnnotation();
|
||||
return static_cast<SourceUnitAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<SourceUnitAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
string Declaration::sourceUnitName() const
|
||||
@ -76,7 +99,7 @@ ImportAnnotation& ImportDirective::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new ImportAnnotation();
|
||||
return static_cast<ImportAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<ImportAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
TypePointer ImportDirective::type() const
|
||||
@ -109,6 +132,12 @@ FunctionDefinition const* ContractDefinition::constructor() const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ContractDefinition::constructorIsPublic() const
|
||||
{
|
||||
FunctionDefinition const* f = constructor();
|
||||
return !f || f->isPublic();
|
||||
}
|
||||
|
||||
FunctionDefinition const* ContractDefinition::fallbackFunction() const
|
||||
{
|
||||
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
|
||||
@ -189,7 +218,6 @@ void ContractDefinition::setUserDocumentation(Json::Value const& _userDocumentat
|
||||
m_userDocumentation = _userDocumentation;
|
||||
}
|
||||
|
||||
|
||||
vector<Declaration const*> const& ContractDefinition::inheritableMembers() const
|
||||
{
|
||||
if (!m_inheritableMembers)
|
||||
@ -233,14 +261,14 @@ ContractDefinitionAnnotation& ContractDefinition::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new ContractDefinitionAnnotation();
|
||||
return static_cast<ContractDefinitionAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<ContractDefinitionAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
TypeNameAnnotation& TypeName::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new TypeNameAnnotation();
|
||||
return static_cast<TypeNameAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<TypeNameAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
TypePointer StructDefinition::type() const
|
||||
@ -252,7 +280,7 @@ TypeDeclarationAnnotation& StructDefinition::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new TypeDeclarationAnnotation();
|
||||
return static_cast<TypeDeclarationAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<TypeDeclarationAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
TypePointer EnumValue::type() const
|
||||
@ -271,7 +299,46 @@ TypeDeclarationAnnotation& EnumDefinition::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new TypeDeclarationAnnotation();
|
||||
return static_cast<TypeDeclarationAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<TypeDeclarationAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
shared_ptr<FunctionType> FunctionDefinition::functionType(bool _internal) const
|
||||
{
|
||||
if (_internal)
|
||||
{
|
||||
switch (visibility())
|
||||
{
|
||||
case Declaration::Visibility::Default:
|
||||
solAssert(false, "visibility() should not return Default");
|
||||
case Declaration::Visibility::Private:
|
||||
case Declaration::Visibility::Internal:
|
||||
case Declaration::Visibility::Public:
|
||||
return make_shared<FunctionType>(*this, _internal);
|
||||
case Declaration::Visibility::External:
|
||||
return {};
|
||||
default:
|
||||
solAssert(false, "visibility() should not return a Visibility");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (visibility())
|
||||
{
|
||||
case Declaration::Visibility::Default:
|
||||
solAssert(false, "visibility() should not return Default");
|
||||
case Declaration::Visibility::Private:
|
||||
case Declaration::Visibility::Internal:
|
||||
return {};
|
||||
case Declaration::Visibility::Public:
|
||||
case Declaration::Visibility::External:
|
||||
return make_shared<FunctionType>(*this, _internal);
|
||||
default:
|
||||
solAssert(false, "visibility() should not return a Visibility");
|
||||
}
|
||||
}
|
||||
|
||||
// To make the compiler happy
|
||||
return {};
|
||||
}
|
||||
|
||||
TypePointer FunctionDefinition::type() const
|
||||
@ -288,7 +355,7 @@ FunctionDefinitionAnnotation& FunctionDefinition::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new FunctionDefinitionAnnotation();
|
||||
return static_cast<FunctionDefinitionAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<FunctionDefinitionAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
TypePointer ModifierDefinition::type() const
|
||||
@ -300,7 +367,7 @@ ModifierDefinitionAnnotation& ModifierDefinition::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new ModifierDefinitionAnnotation();
|
||||
return static_cast<ModifierDefinitionAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<ModifierDefinitionAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
TypePointer EventDefinition::type() const
|
||||
@ -308,18 +375,26 @@ TypePointer EventDefinition::type() const
|
||||
return make_shared<FunctionType>(*this);
|
||||
}
|
||||
|
||||
std::shared_ptr<FunctionType> EventDefinition::functionType(bool _internal) const
|
||||
{
|
||||
if (_internal)
|
||||
return make_shared<FunctionType>(*this);
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
EventDefinitionAnnotation& EventDefinition::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new EventDefinitionAnnotation();
|
||||
return static_cast<EventDefinitionAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<EventDefinitionAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
UserDefinedTypeNameAnnotation& UserDefinedTypeName::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new UserDefinedTypeNameAnnotation();
|
||||
return static_cast<UserDefinedTypeNameAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<UserDefinedTypeNameAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
bool VariableDeclaration::isLValue() const
|
||||
@ -365,72 +440,110 @@ TypePointer VariableDeclaration::type() const
|
||||
return annotation().type;
|
||||
}
|
||||
|
||||
shared_ptr<FunctionType> VariableDeclaration::functionType(bool _internal) const
|
||||
{
|
||||
if (_internal)
|
||||
return {};
|
||||
switch (visibility())
|
||||
{
|
||||
case Declaration::Visibility::Default:
|
||||
solAssert(false, "visibility() should not return Default");
|
||||
case Declaration::Visibility::Private:
|
||||
case Declaration::Visibility::Internal:
|
||||
return {};
|
||||
case Declaration::Visibility::Public:
|
||||
case Declaration::Visibility::External:
|
||||
return make_shared<FunctionType>(*this);
|
||||
default:
|
||||
solAssert(false, "visibility() should not return a Visibility");
|
||||
}
|
||||
|
||||
// To make the compiler happy
|
||||
return {};
|
||||
}
|
||||
|
||||
VariableDeclarationAnnotation& VariableDeclaration::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new VariableDeclarationAnnotation();
|
||||
return static_cast<VariableDeclarationAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<VariableDeclarationAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
StatementAnnotation& Statement::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new StatementAnnotation();
|
||||
return static_cast<StatementAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<StatementAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
InlineAssemblyAnnotation& InlineAssembly::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new InlineAssemblyAnnotation();
|
||||
return static_cast<InlineAssemblyAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<InlineAssemblyAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
ReturnAnnotation& Return::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new ReturnAnnotation();
|
||||
return static_cast<ReturnAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<ReturnAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
VariableDeclarationStatementAnnotation& VariableDeclarationStatement::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new VariableDeclarationStatementAnnotation();
|
||||
return static_cast<VariableDeclarationStatementAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<VariableDeclarationStatementAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
ExpressionAnnotation& Expression::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new ExpressionAnnotation();
|
||||
return static_cast<ExpressionAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<ExpressionAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
MemberAccessAnnotation& MemberAccess::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new MemberAccessAnnotation();
|
||||
return static_cast<MemberAccessAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<MemberAccessAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
BinaryOperationAnnotation& BinaryOperation::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new BinaryOperationAnnotation();
|
||||
return static_cast<BinaryOperationAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<BinaryOperationAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
FunctionCallAnnotation& FunctionCall::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new FunctionCallAnnotation();
|
||||
return static_cast<FunctionCallAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<FunctionCallAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
IdentifierAnnotation& Identifier::annotation() const
|
||||
{
|
||||
if (!m_annotation)
|
||||
m_annotation = new IdentifierAnnotation();
|
||||
return static_cast<IdentifierAnnotation&>(*m_annotation);
|
||||
return dynamic_cast<IdentifierAnnotation&>(*m_annotation);
|
||||
}
|
||||
|
||||
bool Literal::looksLikeAddress() const
|
||||
{
|
||||
if (subDenomination() != SubDenomination::None)
|
||||
return false;
|
||||
|
||||
string lit = value();
|
||||
return lit.substr(0, 2) == "0x" && abs(int(lit.length()) - 42) <= 1;
|
||||
}
|
||||
|
||||
bool Literal::passesAddressChecksum() const
|
||||
{
|
||||
string lit = value();
|
||||
solAssert(lit.substr(0, 2) == "0x", "Expected hex prefix");
|
||||
return dev::passesAddressChecksum(lit, true);
|
||||
}
|
||||
|
||||
@ -57,6 +57,11 @@ public:
|
||||
explicit ASTNode(SourceLocation const& _location);
|
||||
virtual ~ASTNode();
|
||||
|
||||
/// @returns an identifier of this AST node that is unique for a single compilation run.
|
||||
size_t id() const { return m_id; }
|
||||
/// Resets the global ID counter. This invalidates all previous IDs.
|
||||
static void resetID();
|
||||
|
||||
virtual void accept(ASTVisitor& _visitor) = 0;
|
||||
virtual void accept(ASTConstVisitor& _visitor) const = 0;
|
||||
template <class T>
|
||||
@ -94,6 +99,7 @@ public:
|
||||
///@}
|
||||
|
||||
protected:
|
||||
size_t const m_id = 0;
|
||||
/// Annotation - is specialised in derived classes, is created upon request (because of polymorphism).
|
||||
mutable ASTAnnotation* m_annotation = nullptr;
|
||||
|
||||
@ -161,6 +167,7 @@ public:
|
||||
/// @returns the source name this declaration is present in.
|
||||
/// Can be combined with annotation().canonicalName to form a globally unique name.
|
||||
std::string sourceUnitName() const;
|
||||
std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); }
|
||||
|
||||
virtual bool isLValue() const { return false; }
|
||||
virtual bool isPartOfExternalInterface() const { return false; }
|
||||
@ -171,6 +178,10 @@ public:
|
||||
/// This can only be called once types of variable declarations have already been resolved.
|
||||
virtual TypePointer type() const = 0;
|
||||
|
||||
/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
|
||||
/// @returns null when it is not accessible as a function.
|
||||
virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const { return {}; }
|
||||
|
||||
protected:
|
||||
virtual Visibility defaultVisibility() const { return Visibility::Public; }
|
||||
|
||||
@ -345,6 +356,8 @@ public:
|
||||
|
||||
/// Returns the constructor or nullptr if no constructor was specified.
|
||||
FunctionDefinition const* constructor() const;
|
||||
/// @returns true iff the constructor of this contract is public (or non-existing).
|
||||
bool constructorIsPublic() const;
|
||||
/// Returns the fallback function or nullptr if no fallback function was specified.
|
||||
FunctionDefinition const* fallbackFunction() const;
|
||||
|
||||
@ -581,6 +594,10 @@ public:
|
||||
|
||||
virtual TypePointer type() const override;
|
||||
|
||||
/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
|
||||
/// @returns null when it is not accessible as a function.
|
||||
virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const override;
|
||||
|
||||
virtual FunctionDefinitionAnnotation& annotation() const override;
|
||||
|
||||
private:
|
||||
@ -593,7 +610,7 @@ private:
|
||||
|
||||
/**
|
||||
* Declaration of a variable. This can be used in various places, e.g. in function parameter
|
||||
* lists, struct definitions and even function bodys.
|
||||
* lists, struct definitions and even function bodies.
|
||||
*/
|
||||
class VariableDeclaration: public Declaration
|
||||
{
|
||||
@ -643,6 +660,10 @@ public:
|
||||
|
||||
virtual TypePointer type() const override;
|
||||
|
||||
/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
|
||||
/// @returns null when it is not accessible as a function.
|
||||
virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const override;
|
||||
|
||||
virtual VariableDeclarationAnnotation& annotation() const override;
|
||||
|
||||
protected:
|
||||
@ -740,6 +761,7 @@ public:
|
||||
bool isAnonymous() const { return m_anonymous; }
|
||||
|
||||
virtual TypePointer type() const override;
|
||||
virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const override;
|
||||
|
||||
virtual EventDefinitionAnnotation& annotation() const override;
|
||||
|
||||
@ -849,7 +871,10 @@ public:
|
||||
std::vector<ASTPointer<VariableDeclaration>> const& parameterTypes() const { return m_parameterTypes->parameters(); }
|
||||
std::vector<ASTPointer<VariableDeclaration>> const& returnParameterTypes() const { return m_returnTypes->parameters(); }
|
||||
|
||||
Declaration::Visibility visibility() const { return m_visibility; }
|
||||
Declaration::Visibility visibility() const
|
||||
{
|
||||
return m_visibility == Declaration::Visibility::Default ? Declaration::Visibility::Internal : m_visibility;
|
||||
}
|
||||
bool isDeclaredConst() const { return m_isDeclaredConst; }
|
||||
bool isPayable() const { return m_isPayable; }
|
||||
|
||||
@ -1561,6 +1586,11 @@ public:
|
||||
|
||||
SubDenomination subDenomination() const { return m_subDenomination; }
|
||||
|
||||
/// @returns true if this looks like a checksummed address.
|
||||
bool looksLikeAddress() const;
|
||||
/// @returns true if it passes the address checksum test.
|
||||
bool passesAddressChecksum() const;
|
||||
|
||||
private:
|
||||
Token::Value m_token;
|
||||
ASTPointer<ASTString> m_value;
|
||||
|
||||
@ -156,6 +156,8 @@ struct ExpressionAnnotation: ASTAnnotation
|
||||
TypePointer type;
|
||||
/// Whether the expression is a constant variable
|
||||
bool isConstant = false;
|
||||
/// Whether the expression is pure, i.e. compile-time constant.
|
||||
bool isPure = false;
|
||||
/// Whether it is an LValue (i.e. something that can be assigned to).
|
||||
bool isLValue = false;
|
||||
/// Whether the expression is used in a context where the LValue is actually required.
|
||||
|
||||
@ -42,7 +42,7 @@ void ASTJsonConverter::addJsonNode(
|
||||
{
|
||||
Json::Value node;
|
||||
|
||||
node["id"] = reinterpret_cast<Json::UInt64>(&_node);
|
||||
node["id"] = Json::UInt64(_node.id());
|
||||
node["src"] = sourceLocationToString(_node.location());
|
||||
node["name"] = _nodeName;
|
||||
if (_attributes.size() != 0)
|
||||
@ -124,7 +124,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
|
||||
{
|
||||
Json::Value linearizedBaseContracts(Json::arrayValue);
|
||||
for (auto const& baseContract: _node.annotation().linearizedBaseContracts)
|
||||
linearizedBaseContracts.append(reinterpret_cast<Json::UInt64>(baseContract));
|
||||
linearizedBaseContracts.append(Json::UInt64(baseContract->id()));
|
||||
addJsonNode(_node, "ContractDefinition", {
|
||||
make_pair("name", _node.name()),
|
||||
make_pair("isLibrary", _node.isLibrary()),
|
||||
|
||||
@ -21,15 +21,23 @@
|
||||
*/
|
||||
|
||||
#include <libsolidity/ast/Types.h>
|
||||
#include <limits>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
#include <boost/range/adaptor/sliced.hpp>
|
||||
|
||||
#include <libsolidity/interface/Utils.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
|
||||
#include <libdevcore/CommonIO.h>
|
||||
#include <libdevcore/CommonData.h>
|
||||
#include <libdevcore/SHA3.h>
|
||||
#include <libdevcore/UTF8.h>
|
||||
#include <libsolidity/interface/Utils.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
#include <boost/range/adaptor/sliced.hpp>
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
|
||||
#include <limits>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
@ -117,6 +125,51 @@ u256 const& MemberList::storageSize() const
|
||||
return m_storageOffsets->storageSize();
|
||||
}
|
||||
|
||||
/// Helper functions for type identifier
|
||||
namespace
|
||||
{
|
||||
|
||||
string parenthesizeIdentifier(string const& _internal)
|
||||
{
|
||||
return "$_" + _internal + "_$";
|
||||
}
|
||||
|
||||
template <class Range>
|
||||
string identifierList(Range const&& _list)
|
||||
{
|
||||
return parenthesizeIdentifier(boost::algorithm::join(_list, "_$_"));
|
||||
}
|
||||
|
||||
string identifier(TypePointer const& _type)
|
||||
{
|
||||
return _type ? _type->identifier() : "";
|
||||
}
|
||||
|
||||
string identifierList(vector<TypePointer> const& _list)
|
||||
{
|
||||
return identifierList(_list | boost::adaptors::transformed(identifier));
|
||||
}
|
||||
|
||||
string identifierList(TypePointer const& _type)
|
||||
{
|
||||
return parenthesizeIdentifier(identifier(_type));
|
||||
}
|
||||
|
||||
string identifierList(TypePointer const& _type1, TypePointer const& _type2)
|
||||
{
|
||||
TypePointers list;
|
||||
list.push_back(_type1);
|
||||
list.push_back(_type2);
|
||||
return identifierList(list);
|
||||
}
|
||||
|
||||
string parenthesizeUserIdentifier(string const& _internal)
|
||||
{
|
||||
return parenthesizeIdentifier(boost::algorithm::replace_all_copy(_internal, "$", "$$$"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type)
|
||||
{
|
||||
solAssert(Token::isElementaryTypeName(_type.token()),
|
||||
@ -200,9 +253,9 @@ TypePointer Type::commonType(TypePointer const& _a, TypePointer const& _b)
|
||||
{
|
||||
if (!_a || !_b)
|
||||
return TypePointer();
|
||||
else if (_b->isImplicitlyConvertibleTo(*_a->mobileType()))
|
||||
else if (_a->mobileType() && _b->isImplicitlyConvertibleTo(*_a->mobileType()))
|
||||
return _a->mobileType();
|
||||
else if (_a->isImplicitlyConvertibleTo(*_b->mobileType()))
|
||||
else if (_b->mobileType() && _a->isImplicitlyConvertibleTo(*_b->mobileType()))
|
||||
return _b->mobileType();
|
||||
else
|
||||
return TypePointer();
|
||||
@ -272,7 +325,15 @@ IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier):
|
||||
solAssert(
|
||||
m_bits > 0 && m_bits <= 256 && m_bits % 8 == 0,
|
||||
"Invalid bit number for integer type: " + dev::toString(_bits)
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
string IntegerType::identifier() const
|
||||
{
|
||||
if (isAddress())
|
||||
return "t_address";
|
||||
else
|
||||
return "t_" + string(isSigned() ? "" : "u") + "int" + std::to_string(numBits());
|
||||
}
|
||||
|
||||
bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
|
||||
@ -345,6 +406,14 @@ string IntegerType::toString(bool) const
|
||||
return prefix + dev::toString(m_bits);
|
||||
}
|
||||
|
||||
u256 IntegerType::literalValue(Literal const* _literal) const
|
||||
{
|
||||
solAssert(m_modifier == Modifier::Address, "");
|
||||
solAssert(_literal, "");
|
||||
solAssert(_literal->value().substr(0, 2) == "0x", "");
|
||||
return u256(_literal->value());
|
||||
}
|
||||
|
||||
TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const
|
||||
{
|
||||
if (
|
||||
@ -396,7 +465,8 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons
|
||||
{"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::Bare, true, false, true)},
|
||||
{"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::BareCallCode, true, false, true)},
|
||||
{"delegatecall", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Location::BareDelegateCall, true)},
|
||||
{"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Location::Send)}
|
||||
{"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Location::Send)},
|
||||
{"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Location::Transfer)}
|
||||
};
|
||||
else
|
||||
return MemberList::MemberMap();
|
||||
@ -412,7 +482,12 @@ FixedPointType::FixedPointType(int _integerBits, int _fractionalBits, FixedPoint
|
||||
m_fractionalBits % 8 == 0,
|
||||
"Invalid bit number(s) for fixed type: " +
|
||||
dev::toString(_integerBits) + "x" + dev::toString(_fractionalBits)
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
string FixedPointType::identifier() const
|
||||
{
|
||||
return "t_" + string(isSigned() ? "" : "u") + "fixed" + std::to_string(integerBits()) + "x" + std::to_string(fractionalBits());
|
||||
}
|
||||
|
||||
bool FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const
|
||||
@ -497,39 +572,99 @@ TypePointer FixedPointType::binaryOperatorResult(Token::Value _operator, TypePoi
|
||||
return commonType;
|
||||
}
|
||||
|
||||
tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal)
|
||||
tuple<bool, rational> RationalNumberType::parseRational(string const& _value)
|
||||
{
|
||||
rational x;
|
||||
rational value;
|
||||
try
|
||||
{
|
||||
rational numerator;
|
||||
rational denominator(1);
|
||||
|
||||
auto radixPoint = find(_literal.value().begin(), _literal.value().end(), '.');
|
||||
if (radixPoint != _literal.value().end())
|
||||
auto radixPoint = find(_value.begin(), _value.end(), '.');
|
||||
|
||||
if (radixPoint != _value.end())
|
||||
{
|
||||
if (
|
||||
!all_of(radixPoint + 1, _literal.value().end(), ::isdigit) ||
|
||||
!all_of(_literal.value().begin(), radixPoint, ::isdigit)
|
||||
!all_of(radixPoint + 1, _value.end(), ::isdigit) ||
|
||||
!all_of(_value.begin(), radixPoint, ::isdigit)
|
||||
)
|
||||
return make_tuple(false, rational(0));
|
||||
//Only decimal notation allowed here, leading zeros would switch to octal.
|
||||
|
||||
// Only decimal notation allowed here, leading zeros would switch to octal.
|
||||
auto fractionalBegin = find_if_not(
|
||||
radixPoint + 1,
|
||||
_literal.value().end(),
|
||||
radixPoint + 1,
|
||||
_value.end(),
|
||||
[](char const& a) { return a == '0'; }
|
||||
);
|
||||
|
||||
denominator = bigint(string(fractionalBegin, _literal.value().end()));
|
||||
rational numerator;
|
||||
rational denominator(1);
|
||||
|
||||
denominator = bigint(string(fractionalBegin, _value.end()));
|
||||
denominator /= boost::multiprecision::pow(
|
||||
bigint(10),
|
||||
distance(radixPoint + 1, _literal.value().end())
|
||||
bigint(10),
|
||||
distance(radixPoint + 1, _value.end())
|
||||
);
|
||||
numerator = bigint(string(_literal.value().begin(), radixPoint));
|
||||
x = numerator + denominator;
|
||||
numerator = bigint(string(_value.begin(), radixPoint));
|
||||
value = numerator + denominator;
|
||||
}
|
||||
else
|
||||
x = bigint(_literal.value());
|
||||
value = bigint(_value);
|
||||
return make_tuple(true, value);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return make_tuple(false, rational(0));
|
||||
}
|
||||
}
|
||||
|
||||
tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal)
|
||||
{
|
||||
rational value;
|
||||
try
|
||||
{
|
||||
auto expPoint = find(_literal.value().begin(), _literal.value().end(), 'e');
|
||||
if (expPoint == _literal.value().end())
|
||||
expPoint = find(_literal.value().begin(), _literal.value().end(), 'E');
|
||||
|
||||
if (boost::starts_with(_literal.value(), "0x"))
|
||||
{
|
||||
// process as hex
|
||||
value = bigint(_literal.value());
|
||||
}
|
||||
else if (expPoint != _literal.value().end())
|
||||
{
|
||||
// parse the exponent
|
||||
bigint exp = bigint(string(expPoint + 1, _literal.value().end()));
|
||||
|
||||
if (exp > numeric_limits<int32_t>::max() || exp < numeric_limits<int32_t>::min())
|
||||
return make_tuple(false, rational(0));
|
||||
|
||||
// parse the base
|
||||
tuple<bool, rational> base = parseRational(string(_literal.value().begin(), expPoint));
|
||||
if (!get<0>(base))
|
||||
return make_tuple(false, rational(0));
|
||||
value = get<1>(base);
|
||||
|
||||
if (exp < 0)
|
||||
{
|
||||
exp *= -1;
|
||||
value /= boost::multiprecision::pow(
|
||||
bigint(10),
|
||||
exp.convert_to<int32_t>()
|
||||
);
|
||||
}
|
||||
else
|
||||
value *= boost::multiprecision::pow(
|
||||
bigint(10),
|
||||
exp.convert_to<int32_t>()
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
// parse as rational number
|
||||
tuple<bool, rational> tmp = parseRational(_literal.value());
|
||||
if (!get<0>(tmp))
|
||||
return tmp;
|
||||
value = get<1>(tmp);
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@ -542,33 +677,33 @@ tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal
|
||||
case Literal::SubDenomination::Second:
|
||||
break;
|
||||
case Literal::SubDenomination::Szabo:
|
||||
x *= bigint("1000000000000");
|
||||
value *= bigint("1000000000000");
|
||||
break;
|
||||
case Literal::SubDenomination::Finney:
|
||||
x *= bigint("1000000000000000");
|
||||
value *= bigint("1000000000000000");
|
||||
break;
|
||||
case Literal::SubDenomination::Ether:
|
||||
x *= bigint("1000000000000000000");
|
||||
value *= bigint("1000000000000000000");
|
||||
break;
|
||||
case Literal::SubDenomination::Minute:
|
||||
x *= bigint("60");
|
||||
value *= bigint("60");
|
||||
break;
|
||||
case Literal::SubDenomination::Hour:
|
||||
x *= bigint("3600");
|
||||
value *= bigint("3600");
|
||||
break;
|
||||
case Literal::SubDenomination::Day:
|
||||
x *= bigint("86400");
|
||||
value *= bigint("86400");
|
||||
break;
|
||||
case Literal::SubDenomination::Week:
|
||||
x *= bigint("604800");
|
||||
value *= bigint("604800");
|
||||
break;
|
||||
case Literal::SubDenomination::Year:
|
||||
x *= bigint("31536000");
|
||||
value *= bigint("31536000");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return make_tuple(true, x);
|
||||
return make_tuple(true, value);
|
||||
}
|
||||
|
||||
bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const
|
||||
@ -576,12 +711,12 @@ bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const
|
||||
if (_convertTo.category() == Category::Integer)
|
||||
{
|
||||
auto targetType = dynamic_cast<IntegerType const*>(&_convertTo);
|
||||
if (m_value == 0)
|
||||
if (m_value == rational(0))
|
||||
return true;
|
||||
if (isFractional())
|
||||
return false;
|
||||
int forSignBit = (targetType->isSigned() ? 1 : 0);
|
||||
if (m_value > 0)
|
||||
if (m_value > rational(0))
|
||||
{
|
||||
if (m_value.numerator() <= (u256(-1) >> (256 - targetType->numBits() + forSignBit)))
|
||||
return true;
|
||||
@ -702,13 +837,13 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
|
||||
value = m_value * other.m_value;
|
||||
break;
|
||||
case Token::Div:
|
||||
if (other.m_value == 0)
|
||||
if (other.m_value == rational(0))
|
||||
return TypePointer();
|
||||
else
|
||||
value = m_value / other.m_value;
|
||||
break;
|
||||
case Token::Mod:
|
||||
if (other.m_value == 0)
|
||||
if (other.m_value == rational(0))
|
||||
return TypePointer();
|
||||
else if (fractional)
|
||||
{
|
||||
@ -770,6 +905,11 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
|
||||
}
|
||||
}
|
||||
|
||||
string RationalNumberType::identifier() const
|
||||
{
|
||||
return "t_rational_" + m_value.numerator().str() + "_by_" + m_value.denominator().str();
|
||||
}
|
||||
|
||||
bool RationalNumberType::operator==(Type const& _other) const
|
||||
{
|
||||
if (_other.category() != category())
|
||||
@ -808,7 +948,7 @@ u256 RationalNumberType::literalValue(Literal const*) const
|
||||
solAssert(shiftedValue <= u256(-1), "Integer constant too large.");
|
||||
solAssert(shiftedValue >= -(bigint(1) << 255), "Number constant too small.");
|
||||
|
||||
if (m_value >= 0)
|
||||
if (m_value >= rational(0))
|
||||
value = u256(shiftedValue);
|
||||
else
|
||||
value = s2u(s256(shiftedValue));
|
||||
@ -909,6 +1049,13 @@ bool StringLiteralType::isImplicitlyConvertibleTo(Type const& _convertTo) const
|
||||
return false;
|
||||
}
|
||||
|
||||
string StringLiteralType::identifier() const
|
||||
{
|
||||
// Since we have to return a valid identifier and the string itself may contain
|
||||
// anything, we hash it.
|
||||
return "t_stringliteral_" + toHex(keccak256(m_value).asBytes());
|
||||
}
|
||||
|
||||
bool StringLiteralType::operator==(const Type& _other) const
|
||||
{
|
||||
if (_other.category() != category())
|
||||
@ -1002,6 +1149,11 @@ MemberList::MemberMap FixedBytesType::nativeMembers(const ContractDefinition*) c
|
||||
return MemberList::MemberMap{MemberList::Member{"length", make_shared<IntegerType>(8)}};
|
||||
}
|
||||
|
||||
string FixedBytesType::identifier() const
|
||||
{
|
||||
return "t_bytes" + std::to_string(m_bytes);
|
||||
}
|
||||
|
||||
bool FixedBytesType::operator==(Type const& _other) const
|
||||
{
|
||||
if (_other.category() != category())
|
||||
@ -1115,6 +1267,20 @@ string ReferenceType::stringForReferencePart() const
|
||||
return "";
|
||||
}
|
||||
|
||||
string ReferenceType::identifierLocationSuffix() const
|
||||
{
|
||||
string id;
|
||||
if (location() == DataLocation::Storage)
|
||||
id += "_storage";
|
||||
else if (location() == DataLocation::Memory)
|
||||
id += "_memory";
|
||||
else
|
||||
id += "_calldata";
|
||||
if (isPointer())
|
||||
id += "_ptr";
|
||||
return id;
|
||||
}
|
||||
|
||||
bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const
|
||||
{
|
||||
if (_convertTo.category() != category())
|
||||
@ -1170,6 +1336,27 @@ bool ArrayType::isExplicitlyConvertibleTo(const Type& _convertTo) const
|
||||
return true;
|
||||
}
|
||||
|
||||
string ArrayType::identifier() const
|
||||
{
|
||||
string id;
|
||||
if (isString())
|
||||
id = "t_string";
|
||||
else if (isByteArray())
|
||||
id = "t_bytes";
|
||||
else
|
||||
{
|
||||
id = "t_array";
|
||||
id += identifierList(baseType());
|
||||
if (isDynamicallySized())
|
||||
id += "dyn";
|
||||
else
|
||||
id += length().str();
|
||||
}
|
||||
id += identifierLocationSuffix();
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
bool ArrayType::operator==(Type const& _other) const
|
||||
{
|
||||
if (_other.category() != category())
|
||||
@ -1184,7 +1371,7 @@ bool ArrayType::operator==(Type const& _other) const
|
||||
return false;
|
||||
if (*other.baseType() != *baseType())
|
||||
return false;
|
||||
return isDynamicallySized() || length() == other.length();
|
||||
return isDynamicallySized() || length() == other.length();
|
||||
}
|
||||
|
||||
unsigned ArrayType::calldataEncodedSize(bool _padded) const
|
||||
@ -1356,6 +1543,11 @@ TypePointer ArrayType::copyForLocation(DataLocation _location, bool _isPointer)
|
||||
return copy;
|
||||
}
|
||||
|
||||
string ContractType::identifier() const
|
||||
{
|
||||
return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + std::to_string(m_contract.id());
|
||||
}
|
||||
|
||||
bool ContractType::operator==(Type const& _other) const
|
||||
{
|
||||
if (_other.category() != category())
|
||||
@ -1465,6 +1657,11 @@ bool StructType::isImplicitlyConvertibleTo(const Type& _convertTo) const
|
||||
return this->m_struct == convertTo.m_struct;
|
||||
}
|
||||
|
||||
string StructType::identifier() const
|
||||
{
|
||||
return "t_struct" + parenthesizeUserIdentifier(m_struct.name()) + std::to_string(m_struct.id()) + identifierLocationSuffix();
|
||||
}
|
||||
|
||||
bool StructType::operator==(Type const& _other) const
|
||||
{
|
||||
if (_other.category() != category())
|
||||
@ -1517,6 +1714,7 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const
|
||||
for (ASTPointer<VariableDeclaration> const& variable: m_struct.members())
|
||||
{
|
||||
TypePointer type = variable->annotation().type;
|
||||
solAssert(type, "");
|
||||
// Skip all mapping members if we are not in storage.
|
||||
if (location() != DataLocation::Storage && !type->canLiveOutsideStorage())
|
||||
continue;
|
||||
@ -1605,6 +1803,11 @@ TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const
|
||||
return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer();
|
||||
}
|
||||
|
||||
string EnumType::identifier() const
|
||||
{
|
||||
return "t_enum" + parenthesizeUserIdentifier(m_enum.name()) + std::to_string(m_enum.id());
|
||||
}
|
||||
|
||||
bool EnumType::operator==(Type const& _other) const
|
||||
{
|
||||
if (_other.category() != category())
|
||||
@ -1686,6 +1889,11 @@ bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const
|
||||
return false;
|
||||
}
|
||||
|
||||
string TupleType::identifier() const
|
||||
{
|
||||
return "t_tuple" + identifierList(components());
|
||||
}
|
||||
|
||||
bool TupleType::operator==(Type const& _other) const
|
||||
{
|
||||
if (auto tupleType = dynamic_cast<TupleType const*>(&_other))
|
||||
@ -1750,7 +1958,10 @@ TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) cons
|
||||
size_t si = fillRight ? i : components().size() - i - 1;
|
||||
size_t ti = fillRight ? i : targetComponents.size() - i - 1;
|
||||
if (components()[si] && targetComponents[ti])
|
||||
{
|
||||
tempComponents[ti] = components()[si]->closestTemporaryType(targetComponents[ti]);
|
||||
solAssert(tempComponents[ti], "");
|
||||
}
|
||||
}
|
||||
return make_shared<TupleType>(tempComponents);
|
||||
}
|
||||
@ -1819,6 +2030,8 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl):
|
||||
if (auto structType = dynamic_cast<StructType const*>(returnType.get()))
|
||||
{
|
||||
for (auto const& member: structType->members(nullptr))
|
||||
{
|
||||
solAssert(member.type, "");
|
||||
if (member.type->category() != Category::Mapping)
|
||||
{
|
||||
if (auto arrayType = dynamic_cast<ArrayType const*>(member.type.get()))
|
||||
@ -1827,6 +2040,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl):
|
||||
retParams.push_back(member.type);
|
||||
retParamNames.push_back(member.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1934,6 +2148,55 @@ TypePointers FunctionType::parameterTypes() const
|
||||
return TypePointers(m_parameterTypes.cbegin() + 1, m_parameterTypes.cend());
|
||||
}
|
||||
|
||||
string FunctionType::identifier() const
|
||||
{
|
||||
string id = "t_function_";
|
||||
switch (location())
|
||||
{
|
||||
case Location::Internal: id += "internal"; break;
|
||||
case Location::External: id += "external"; break;
|
||||
case Location::CallCode: id += "callcode"; break;
|
||||
case Location::DelegateCall: id += "delegatecall"; break;
|
||||
case Location::Bare: id += "bare"; break;
|
||||
case Location::BareCallCode: id += "barecallcode"; break;
|
||||
case Location::BareDelegateCall: id += "baredelegatecall"; break;
|
||||
case Location::Creation: id += "creation"; break;
|
||||
case Location::Send: id += "send"; break;
|
||||
case Location::Transfer: id += "transfer"; break;
|
||||
case Location::SHA3: id += "sha3"; break;
|
||||
case Location::Selfdestruct: id += "selfdestruct"; break;
|
||||
case Location::Revert: id += "revert"; break;
|
||||
case Location::ECRecover: id += "ecrecover"; break;
|
||||
case Location::SHA256: id += "sha256"; break;
|
||||
case Location::RIPEMD160: id += "ripemd160"; break;
|
||||
case Location::Log0: id += "log0"; break;
|
||||
case Location::Log1: id += "log1"; break;
|
||||
case Location::Log2: id += "log2"; break;
|
||||
case Location::Log3: id += "log3"; break;
|
||||
case Location::Log4: id += "log4"; break;
|
||||
case Location::Event: id += "event"; break;
|
||||
case Location::SetGas: id += "setgas"; break;
|
||||
case Location::SetValue: id += "setvalue"; break;
|
||||
case Location::BlockHash: id += "blockhash"; break;
|
||||
case Location::AddMod: id += "addmod"; break;
|
||||
case Location::MulMod: id += "mulmod"; break;
|
||||
case Location::ArrayPush: id += "arraypush"; break;
|
||||
case Location::ByteArrayPush: id += "bytearraypush"; break;
|
||||
case Location::ObjectCreation: id += "objectcreation"; break;
|
||||
default: solAssert(false, "Unknown function location."); break;
|
||||
}
|
||||
if (isConstant())
|
||||
id += "_constant";
|
||||
id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes);
|
||||
if (m_gasSet)
|
||||
id += "gas";
|
||||
if (m_valueSet)
|
||||
id += "value";
|
||||
if (bound())
|
||||
id += "bound_to" + identifierList(selfType());
|
||||
return id;
|
||||
}
|
||||
|
||||
bool FunctionType::operator==(Type const& _other) const
|
||||
{
|
||||
if (_other.category() != category())
|
||||
@ -1966,6 +2229,17 @@ bool FunctionType::operator==(Type const& _other) const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const
|
||||
{
|
||||
if (m_location == Location::External && _convertTo.category() == Category::Integer)
|
||||
{
|
||||
IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo);
|
||||
if (convertTo.isAddress())
|
||||
return true;
|
||||
}
|
||||
return _convertTo.category() == category();
|
||||
}
|
||||
|
||||
TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const
|
||||
{
|
||||
if (_operator == Token::Value::Delete)
|
||||
@ -2243,6 +2517,18 @@ u256 FunctionType::externalIdentifier() const
|
||||
return FixedHash<4>::Arith(FixedHash<4>(dev::keccak256(externalSignature())));
|
||||
}
|
||||
|
||||
bool FunctionType::isPure() const
|
||||
{
|
||||
return
|
||||
m_location == Location::SHA3 ||
|
||||
m_location == Location::ECRecover ||
|
||||
m_location == Location::SHA256 ||
|
||||
m_location == Location::RIPEMD160 ||
|
||||
m_location == Location::AddMod ||
|
||||
m_location == Location::MulMod ||
|
||||
m_location == Location::ObjectCreation;
|
||||
}
|
||||
|
||||
TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)
|
||||
{
|
||||
TypePointers pointers;
|
||||
@ -2280,7 +2566,7 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound)
|
||||
{
|
||||
auto refType = dynamic_cast<ReferenceType const*>(t.get());
|
||||
if (refType && refType->location() == DataLocation::CallData)
|
||||
parameterTypes.push_back(refType->copyForLocation(DataLocation::Memory, false));
|
||||
parameterTypes.push_back(refType->copyForLocation(DataLocation::Memory, true));
|
||||
else
|
||||
parameterTypes.push_back(t);
|
||||
}
|
||||
@ -2320,25 +2606,7 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound)
|
||||
);
|
||||
}
|
||||
|
||||
vector<string> const FunctionType::parameterTypeNames(bool _addDataLocation) const
|
||||
{
|
||||
vector<string> names;
|
||||
for (TypePointer const& t: parameterTypes())
|
||||
names.push_back(t->canonicalName(_addDataLocation));
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
vector<string> const FunctionType::returnParameterTypeNames(bool _addDataLocation) const
|
||||
{
|
||||
vector<string> names;
|
||||
for (TypePointer const& t: m_returnParameterTypes)
|
||||
names.push_back(t->canonicalName(_addDataLocation));
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
TypePointer FunctionType::selfType() const
|
||||
TypePointer const& FunctionType::selfType() const
|
||||
{
|
||||
solAssert(bound(), "Function is not bound.");
|
||||
solAssert(m_parameterTypes.size() > 0, "Function has no self type.");
|
||||
@ -2354,6 +2622,11 @@ ASTPointer<ASTString> FunctionType::documentation() const
|
||||
return ASTPointer<ASTString>();
|
||||
}
|
||||
|
||||
string MappingType::identifier() const
|
||||
{
|
||||
return "t_mapping" + identifierList(m_keyType, m_valueType);
|
||||
}
|
||||
|
||||
bool MappingType::operator==(Type const& _other) const
|
||||
{
|
||||
if (_other.category() != category())
|
||||
@ -2372,6 +2645,11 @@ string MappingType::canonicalName(bool) const
|
||||
return "mapping(" + keyType()->canonicalName(false) + " => " + valueType()->canonicalName(false) + ")";
|
||||
}
|
||||
|
||||
string TypeType::identifier() const
|
||||
{
|
||||
return "t_type" + identifierList(actualType());
|
||||
}
|
||||
|
||||
bool TypeType::operator==(Type const& _other) const
|
||||
{
|
||||
if (_other.category() != category())
|
||||
@ -2456,6 +2734,11 @@ u256 ModifierType::storageSize() const
|
||||
<< errinfo_comment("Storage size of non-storable type type requested."));
|
||||
}
|
||||
|
||||
string ModifierType::identifier() const
|
||||
{
|
||||
return "t_modifier" + identifierList(m_parameterTypes);
|
||||
}
|
||||
|
||||
bool ModifierType::operator==(Type const& _other) const
|
||||
{
|
||||
if (_other.category() != category())
|
||||
@ -2480,6 +2763,11 @@ string ModifierType::toString(bool _short) const
|
||||
return name + ")";
|
||||
}
|
||||
|
||||
string ModuleType::identifier() const
|
||||
{
|
||||
return "t_module_" + std::to_string(m_sourceUnit.id());
|
||||
}
|
||||
|
||||
bool ModuleType::operator==(Type const& _other) const
|
||||
{
|
||||
if (_other.category() != category())
|
||||
@ -2501,6 +2789,22 @@ string ModuleType::toString(bool) const
|
||||
return string("module \"") + m_sourceUnit.annotation().path + string("\"");
|
||||
}
|
||||
|
||||
string MagicType::identifier() const
|
||||
{
|
||||
switch (m_kind)
|
||||
{
|
||||
case Kind::Block:
|
||||
return "t_magic_block";
|
||||
case Kind::Message:
|
||||
return "t_magic_message";
|
||||
case Kind::Transaction:
|
||||
return "t_magic_transaction";
|
||||
default:
|
||||
solAssert(false, "Unknown kind of magic");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
bool MagicType::operator==(Type const& _other) const
|
||||
{
|
||||
if (_other.category() != category())
|
||||
|
||||
@ -22,18 +22,21 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <boost/rational.hpp>
|
||||
#include <libdevcore/Common.h>
|
||||
#include <libdevcore/CommonIO.h>
|
||||
#include <libsolidity/interface/Exceptions.h>
|
||||
#include <libsolidity/ast/ASTForward.h>
|
||||
#include <libsolidity/parsing/Token.h>
|
||||
|
||||
#include <libdevcore/Common.h>
|
||||
#include <libdevcore/CommonIO.h>
|
||||
#include <libdevcore/UndefMacros.h>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <boost/rational.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
@ -155,6 +158,13 @@ public:
|
||||
static TypePointer commonType(TypePointer const& _a, TypePointer const& _b);
|
||||
|
||||
virtual Category category() const = 0;
|
||||
/// @returns a valid solidity identifier such that two types should compare equal if and
|
||||
/// only if they have the same identifier.
|
||||
/// The identifier should start with "t_".
|
||||
/// More complex identifier strings use "parentheses", where $_ is interpreted as as
|
||||
/// "opening parenthesis", _$ as "closing parenthesis", _$_ as "comma" and any $ that
|
||||
/// appears as part of a user-supplied identifier is escaped as _$$$_.
|
||||
virtual std::string identifier() const = 0;
|
||||
virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; }
|
||||
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const
|
||||
{
|
||||
@ -288,6 +298,7 @@ public:
|
||||
|
||||
explicit IntegerType(int _bits, Modifier _modifier = Modifier::Unsigned);
|
||||
|
||||
virtual std::string identifier() const override;
|
||||
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
|
||||
@ -303,6 +314,8 @@ public:
|
||||
|
||||
virtual std::string toString(bool _short) const override;
|
||||
|
||||
virtual u256 literalValue(Literal const* _literal) const override;
|
||||
|
||||
virtual TypePointer encodingType() const override { return shared_from_this(); }
|
||||
virtual TypePointer interfaceType(bool) const override { return shared_from_this(); }
|
||||
|
||||
@ -329,6 +342,7 @@ public:
|
||||
|
||||
explicit FixedPointType(int _integerBits, int _fractionalBits, Modifier _modifier = Modifier::Unsigned);
|
||||
|
||||
virtual std::string identifier() const override;
|
||||
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
|
||||
@ -378,6 +392,7 @@ public:
|
||||
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
|
||||
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
|
||||
|
||||
virtual std::string identifier() const override;
|
||||
virtual bool operator==(Type const& _other) const override;
|
||||
|
||||
virtual bool canBeStored() const override { return false; }
|
||||
@ -396,8 +411,14 @@ public:
|
||||
/// @returns true if the value is not an integer.
|
||||
bool isFractional() const { return m_value.denominator() != 1; }
|
||||
|
||||
/// @returns true if the value is negative.
|
||||
bool isNegative() const { return m_value < 0; }
|
||||
|
||||
private:
|
||||
rational m_value;
|
||||
|
||||
/// @returns true if the literal is a valid rational number.
|
||||
static std::tuple<bool, rational> parseRational(std::string const& _value);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -416,6 +437,7 @@ public:
|
||||
return TypePointer();
|
||||
}
|
||||
|
||||
virtual std::string identifier() const override;
|
||||
virtual bool operator==(Type const& _other) const override;
|
||||
|
||||
virtual bool canBeStored() const override { return false; }
|
||||
@ -449,6 +471,7 @@ public:
|
||||
|
||||
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
virtual std::string identifier() const override;
|
||||
virtual bool operator==(Type const& _other) const override;
|
||||
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
|
||||
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
|
||||
@ -476,6 +499,7 @@ class BoolType: public Type
|
||||
public:
|
||||
BoolType() {}
|
||||
virtual Category category() const override { return Category::Bool; }
|
||||
virtual std::string identifier() const override { return "t_bool"; }
|
||||
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
|
||||
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
|
||||
|
||||
@ -533,6 +557,8 @@ protected:
|
||||
TypePointer copyForLocationIfReference(TypePointer const& _type) const;
|
||||
/// @returns a human-readable description of the reference part of the type.
|
||||
std::string stringForReferencePart() const;
|
||||
/// @returns the suffix computed from the reference part to be used by identifier();
|
||||
std::string identifierLocationSuffix() const;
|
||||
|
||||
DataLocation m_location = DataLocation::Storage;
|
||||
bool m_isPointer = true;
|
||||
@ -573,6 +599,7 @@ public:
|
||||
|
||||
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
virtual std::string identifier() const override;
|
||||
virtual bool operator==(const Type& _other) const override;
|
||||
virtual unsigned calldataEncodedSize(bool _padded) const override;
|
||||
virtual bool isDynamicallySized() const override { return m_hasDynamicLength; }
|
||||
@ -622,6 +649,7 @@ public:
|
||||
/// Contracts can be converted to themselves and to integers.
|
||||
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
|
||||
virtual std::string identifier() const override;
|
||||
virtual bool operator==(Type const& _other) const override;
|
||||
virtual unsigned calldataEncodedSize(bool _padded ) const override
|
||||
{
|
||||
@ -677,6 +705,7 @@ public:
|
||||
explicit StructType(StructDefinition const& _struct, DataLocation _location = DataLocation::Storage):
|
||||
ReferenceType(_location), m_struct(_struct) {}
|
||||
virtual bool isImplicitlyConvertibleTo(const Type& _convertTo) const override;
|
||||
virtual std::string identifier() const override;
|
||||
virtual bool operator==(Type const& _other) const override;
|
||||
virtual unsigned calldataEncodedSize(bool _padded) const override;
|
||||
u256 memorySize() const;
|
||||
@ -720,6 +749,7 @@ public:
|
||||
virtual Category category() const override { return Category::Enum; }
|
||||
explicit EnumType(EnumDefinition const& _enum): m_enum(_enum) {}
|
||||
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
|
||||
virtual std::string identifier() const override;
|
||||
virtual bool operator==(Type const& _other) const override;
|
||||
virtual unsigned calldataEncodedSize(bool _padded) const override
|
||||
{
|
||||
@ -760,6 +790,7 @@ public:
|
||||
virtual Category category() const override { return Category::Tuple; }
|
||||
explicit TupleType(std::vector<TypePointer> const& _types = std::vector<TypePointer>()): m_components(_types) {}
|
||||
virtual bool isImplicitlyConvertibleTo(Type const& _other) const override;
|
||||
virtual std::string identifier() const override;
|
||||
virtual bool operator==(Type const& _other) const override;
|
||||
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
|
||||
virtual std::string toString(bool) const override;
|
||||
@ -791,15 +822,17 @@ public:
|
||||
{
|
||||
Internal, ///< stack-call using plain JUMP
|
||||
External, ///< external call using CALL
|
||||
CallCode, ///< extercnal call using CALLCODE, i.e. not exchanging the storage
|
||||
DelegateCall, ///< extercnal call using DELEGATECALL, i.e. not exchanging the storage
|
||||
CallCode, ///< external call using CALLCODE, i.e. not exchanging the storage
|
||||
DelegateCall, ///< external call using DELEGATECALL, i.e. not exchanging the storage
|
||||
Bare, ///< CALL without function hash
|
||||
BareCallCode, ///< CALLCODE without function hash
|
||||
BareDelegateCall, ///< DELEGATECALL without function hash
|
||||
Creation, ///< external call using CREATE
|
||||
Send, ///< CALL, but without data and gas
|
||||
Transfer, ///< CALL, but without data and throws on error
|
||||
SHA3, ///< SHA3
|
||||
Selfdestruct, ///< SELFDESTRUCT
|
||||
Revert, ///< REVERT
|
||||
ECRecover, ///< CALL to special contract for ecrecover
|
||||
SHA256, ///< CALL to special contract for sha256
|
||||
RIPEMD160, ///< CALL to special contract for ripemd160
|
||||
@ -816,7 +849,9 @@ public:
|
||||
MulMod, ///< MULMOD
|
||||
ArrayPush, ///< .push() to a dynamically sized array in storage
|
||||
ByteArrayPush, ///< .push() to a dynamically sized byte array in storage
|
||||
ObjectCreation ///< array creation using new
|
||||
ObjectCreation, ///< array creation using new
|
||||
Assert, ///< assert()
|
||||
Require ///< require()
|
||||
};
|
||||
|
||||
virtual Category category() const override { return Category::Function; }
|
||||
@ -890,14 +925,14 @@ public:
|
||||
|
||||
TypePointers parameterTypes() const;
|
||||
std::vector<std::string> parameterNames() const;
|
||||
std::vector<std::string> const parameterTypeNames(bool _addDataLocation) const;
|
||||
TypePointers const& returnParameterTypes() const { return m_returnParameterTypes; }
|
||||
std::vector<std::string> const& returnParameterNames() const { return m_returnParameterNames; }
|
||||
std::vector<std::string> const returnParameterTypeNames(bool _addDataLocation) const;
|
||||
/// @returns the "self" parameter type for a bound function
|
||||
TypePointer selfType() const;
|
||||
TypePointer const& selfType() const;
|
||||
|
||||
virtual std::string identifier() const override;
|
||||
virtual bool operator==(Type const& _other) const override;
|
||||
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
|
||||
virtual std::string canonicalName(bool /*_addDataLocation*/) const override;
|
||||
virtual std::string toString(bool _short) const override;
|
||||
@ -941,6 +976,10 @@ public:
|
||||
}
|
||||
bool hasDeclaration() const { return !!m_declaration; }
|
||||
bool isConstant() const { return m_isConstant; }
|
||||
/// @returns true if the the result of this function only depends on its arguments
|
||||
/// and it does not modify the state.
|
||||
/// Currently, this will only return true for internal functions like keccak and ecrecover.
|
||||
bool isPure() const;
|
||||
bool isPayable() const { return m_isPayable; }
|
||||
/// @return A shared pointer of an ASTString.
|
||||
/// Can contain a nullptr in which case indicates absence of documentation
|
||||
@ -995,6 +1034,7 @@ public:
|
||||
MappingType(TypePointer const& _keyType, TypePointer const& _valueType):
|
||||
m_keyType(_keyType), m_valueType(_valueType) {}
|
||||
|
||||
virtual std::string identifier() const override;
|
||||
virtual bool operator==(Type const& _other) const override;
|
||||
virtual std::string toString(bool _short) const override;
|
||||
virtual std::string canonicalName(bool _addDataLocation) const override;
|
||||
@ -1029,6 +1069,7 @@ public:
|
||||
TypePointer const& actualType() const { return m_actualType; }
|
||||
|
||||
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
|
||||
virtual std::string identifier() const override;
|
||||
virtual bool operator==(Type const& _other) const override;
|
||||
virtual bool canBeStored() const override { return false; }
|
||||
virtual u256 storageSize() const override;
|
||||
@ -1056,6 +1097,7 @@ public:
|
||||
virtual u256 storageSize() const override;
|
||||
virtual bool canLiveOutsideStorage() const override { return false; }
|
||||
virtual unsigned sizeOnStack() const override { return 0; }
|
||||
virtual std::string identifier() const override;
|
||||
virtual bool operator==(Type const& _other) const override;
|
||||
virtual std::string toString(bool _short) const override;
|
||||
|
||||
@ -1080,6 +1122,7 @@ public:
|
||||
return TypePointer();
|
||||
}
|
||||
|
||||
virtual std::string identifier() const override;
|
||||
virtual bool operator==(Type const& _other) const override;
|
||||
virtual bool canBeStored() const override { return false; }
|
||||
virtual bool canLiveOutsideStorage() const override { return true; }
|
||||
@ -1109,6 +1152,7 @@ public:
|
||||
return TypePointer();
|
||||
}
|
||||
|
||||
virtual std::string identifier() const override;
|
||||
virtual bool operator==(Type const& _other) const override;
|
||||
virtual bool canBeStored() const override { return false; }
|
||||
virtual bool canLiveOutsideStorage() const override { return true; }
|
||||
@ -1132,6 +1176,7 @@ class InaccessibleDynamicType: public Type
|
||||
public:
|
||||
virtual Category category() const override { return Category::InaccessibleDynamic; }
|
||||
|
||||
virtual std::string identifier() const override { return "t_inaccessible"; }
|
||||
virtual bool isImplicitlyConvertibleTo(Type const&) const override { return false; }
|
||||
virtual bool isExplicitlyConvertibleTo(Type const&) const override { return false; }
|
||||
virtual unsigned calldataEncodedSize(bool _padded) const override { (void)_padded; return 32; }
|
||||
|
||||
@ -40,9 +40,9 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
|
||||
// stack layout: [source_ref] [source length] target_ref (top)
|
||||
solAssert(_targetType.location() == DataLocation::Storage, "");
|
||||
|
||||
IntegerType uint256(256);
|
||||
Type const* targetBaseType = _targetType.isByteArray() ? &uint256 : &(*_targetType.baseType());
|
||||
Type const* sourceBaseType = _sourceType.isByteArray() ? &uint256 : &(*_sourceType.baseType());
|
||||
TypePointer uint256 = make_shared<IntegerType>(256);
|
||||
TypePointer targetBaseType = _targetType.isByteArray() ? uint256 : _targetType.baseType();
|
||||
TypePointer sourceBaseType = _sourceType.isByteArray() ? uint256 : _sourceType.baseType();
|
||||
|
||||
// TODO unroll loop for small sizes
|
||||
|
||||
@ -70,202 +70,216 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
|
||||
}
|
||||
|
||||
// stack: target_ref source_ref source_length
|
||||
m_context << Instruction::DUP3;
|
||||
// stack: target_ref source_ref source_length target_ref
|
||||
retrieveLength(_targetType);
|
||||
// stack: target_ref source_ref source_length target_ref target_length
|
||||
if (_targetType.isDynamicallySized())
|
||||
// store new target length
|
||||
if (!_targetType.isByteArray())
|
||||
// Otherwise, length will be stored below.
|
||||
m_context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE;
|
||||
if (sourceBaseType->category() == Type::Category::Mapping)
|
||||
{
|
||||
solAssert(targetBaseType->category() == Type::Category::Mapping, "");
|
||||
solAssert(_sourceType.location() == DataLocation::Storage, "");
|
||||
// nothing to copy
|
||||
m_context
|
||||
<< Instruction::POP << Instruction::POP
|
||||
<< Instruction::POP << Instruction::POP;
|
||||
return;
|
||||
}
|
||||
// stack: target_ref source_ref source_length target_ref target_length
|
||||
// compute hashes (data positions)
|
||||
m_context << Instruction::SWAP1;
|
||||
if (_targetType.isDynamicallySized())
|
||||
CompilerUtils(m_context).computeHashStatic();
|
||||
// stack: target_ref source_ref source_length target_length target_data_pos
|
||||
m_context << Instruction::SWAP1;
|
||||
convertLengthToSize(_targetType);
|
||||
m_context << Instruction::DUP2 << Instruction::ADD;
|
||||
// stack: target_ref source_ref source_length target_data_pos target_data_end
|
||||
m_context << Instruction::SWAP3;
|
||||
// stack: target_ref target_data_end source_length target_data_pos source_ref
|
||||
|
||||
eth::AssemblyItem copyLoopEndWithoutByteOffset = m_context.newTag();
|
||||
|
||||
// special case for short byte arrays: Store them together with their length.
|
||||
if (_targetType.isByteArray())
|
||||
{
|
||||
// stack: target_ref target_data_end source_length target_data_pos source_ref
|
||||
m_context << Instruction::DUP3 << u256(31) << Instruction::LT;
|
||||
eth::AssemblyItem longByteArray = m_context.appendConditionalJump();
|
||||
// store the short byte array
|
||||
solAssert(_sourceType.isByteArray(), "");
|
||||
if (_sourceType.location() == DataLocation::Storage)
|
||||
TypePointer targetType = _targetType.shared_from_this();
|
||||
TypePointer sourceType = _sourceType.shared_from_this();
|
||||
m_context.callLowLevelFunction(
|
||||
"$copyArrayToStorage_" + sourceType->identifier() + "_to_" + targetType->identifier(),
|
||||
3,
|
||||
1,
|
||||
[=](CompilerContext& _context)
|
||||
{
|
||||
// just copy the slot, it contains length and data
|
||||
m_context << Instruction::DUP1 << Instruction::SLOAD;
|
||||
m_context << Instruction::DUP6 << Instruction::SSTORE;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_context << Instruction::DUP1;
|
||||
CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
|
||||
// stack: target_ref target_data_end source_length target_data_pos source_ref value
|
||||
// clear the lower-order byte - which will hold the length
|
||||
m_context << u256(0xff) << Instruction::NOT << Instruction::AND;
|
||||
// fetch the length and shift it left by one
|
||||
m_context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD;
|
||||
// combine value and length and store them
|
||||
m_context << Instruction::OR << Instruction::DUP6 << Instruction::SSTORE;
|
||||
}
|
||||
// end of special case, jump right into cleaning target data area
|
||||
m_context.appendJumpTo(copyLoopEndWithoutByteOffset);
|
||||
m_context << longByteArray;
|
||||
// Store length (2*length+1)
|
||||
m_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
|
||||
m_context << u256(1) << Instruction::ADD;
|
||||
m_context << Instruction::DUP6 << Instruction::SSTORE;
|
||||
}
|
||||
ArrayUtils utils(_context);
|
||||
ArrayType const& _sourceType = dynamic_cast<ArrayType const&>(*sourceType);
|
||||
ArrayType const& _targetType = dynamic_cast<ArrayType const&>(*targetType);
|
||||
// stack: target_ref source_ref source_length
|
||||
_context << Instruction::DUP3;
|
||||
// stack: target_ref source_ref source_length target_ref
|
||||
utils.retrieveLength(_targetType);
|
||||
// stack: target_ref source_ref source_length target_ref target_length
|
||||
if (_targetType.isDynamicallySized())
|
||||
// store new target length
|
||||
if (!_targetType.isByteArray())
|
||||
// Otherwise, length will be stored below.
|
||||
_context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE;
|
||||
if (sourceBaseType->category() == Type::Category::Mapping)
|
||||
{
|
||||
solAssert(targetBaseType->category() == Type::Category::Mapping, "");
|
||||
solAssert(_sourceType.location() == DataLocation::Storage, "");
|
||||
// nothing to copy
|
||||
_context
|
||||
<< Instruction::POP << Instruction::POP
|
||||
<< Instruction::POP << Instruction::POP;
|
||||
return;
|
||||
}
|
||||
// stack: target_ref source_ref source_length target_ref target_length
|
||||
// compute hashes (data positions)
|
||||
_context << Instruction::SWAP1;
|
||||
if (_targetType.isDynamicallySized())
|
||||
CompilerUtils(_context).computeHashStatic();
|
||||
// stack: target_ref source_ref source_length target_length target_data_pos
|
||||
_context << Instruction::SWAP1;
|
||||
utils.convertLengthToSize(_targetType);
|
||||
_context << Instruction::DUP2 << Instruction::ADD;
|
||||
// stack: target_ref source_ref source_length target_data_pos target_data_end
|
||||
_context << Instruction::SWAP3;
|
||||
// stack: target_ref target_data_end source_length target_data_pos source_ref
|
||||
|
||||
// skip copying if source length is zero
|
||||
m_context << Instruction::DUP3 << Instruction::ISZERO;
|
||||
m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset);
|
||||
eth::AssemblyItem copyLoopEndWithoutByteOffset = _context.newTag();
|
||||
|
||||
if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized())
|
||||
CompilerUtils(m_context).computeHashStatic();
|
||||
// stack: target_ref target_data_end source_length target_data_pos source_data_pos
|
||||
m_context << Instruction::SWAP2;
|
||||
convertLengthToSize(_sourceType);
|
||||
m_context << Instruction::DUP3 << Instruction::ADD;
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end
|
||||
if (haveByteOffsetTarget)
|
||||
m_context << u256(0);
|
||||
if (haveByteOffsetSource)
|
||||
m_context << u256(0);
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
|
||||
eth::AssemblyItem copyLoopStart = m_context.newTag();
|
||||
m_context << copyLoopStart;
|
||||
// check for loop condition
|
||||
m_context
|
||||
<< dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize)
|
||||
<< Instruction::GT << Instruction::ISZERO;
|
||||
eth::AssemblyItem copyLoopEnd = m_context.appendConditionalJump();
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
|
||||
// copy
|
||||
if (sourceBaseType->category() == Type::Category::Array)
|
||||
{
|
||||
solAssert(byteOffsetSize == 0, "Byte offset for array as base type.");
|
||||
auto const& sourceBaseArrayType = dynamic_cast<ArrayType const&>(*sourceBaseType);
|
||||
m_context << Instruction::DUP3;
|
||||
if (sourceBaseArrayType.location() == DataLocation::Memory)
|
||||
m_context << Instruction::MLOAD;
|
||||
m_context << Instruction::DUP3;
|
||||
copyArrayToStorage(dynamic_cast<ArrayType const&>(*targetBaseType), sourceBaseArrayType);
|
||||
m_context << Instruction::POP;
|
||||
}
|
||||
else if (directCopy)
|
||||
{
|
||||
solAssert(byteOffsetSize == 0, "Byte offset for direct copy.");
|
||||
m_context
|
||||
<< Instruction::DUP3 << Instruction::SLOAD
|
||||
<< Instruction::DUP3 << Instruction::SSTORE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note that we have to copy each element on its own in case conversion is involved.
|
||||
// We might copy too much if there is padding at the last element, but this way end
|
||||
// checking is easier.
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
|
||||
m_context << dupInstruction(3 + byteOffsetSize);
|
||||
if (_sourceType.location() == DataLocation::Storage)
|
||||
{
|
||||
// special case for short byte arrays: Store them together with their length.
|
||||
if (_targetType.isByteArray())
|
||||
{
|
||||
// stack: target_ref target_data_end source_length target_data_pos source_ref
|
||||
_context << Instruction::DUP3 << u256(31) << Instruction::LT;
|
||||
eth::AssemblyItem longByteArray = _context.appendConditionalJump();
|
||||
// store the short byte array
|
||||
solAssert(_sourceType.isByteArray(), "");
|
||||
if (_sourceType.location() == DataLocation::Storage)
|
||||
{
|
||||
// just copy the slot, it contains length and data
|
||||
_context << Instruction::DUP1 << Instruction::SLOAD;
|
||||
_context << Instruction::DUP6 << Instruction::SSTORE;
|
||||
}
|
||||
else
|
||||
{
|
||||
_context << Instruction::DUP1;
|
||||
CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
|
||||
// stack: target_ref target_data_end source_length target_data_pos source_ref value
|
||||
// clear the lower-order byte - which will hold the length
|
||||
_context << u256(0xff) << Instruction::NOT << Instruction::AND;
|
||||
// fetch the length and shift it left by one
|
||||
_context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD;
|
||||
// combine value and length and store them
|
||||
_context << Instruction::OR << Instruction::DUP6 << Instruction::SSTORE;
|
||||
}
|
||||
// end of special case, jump right into cleaning target data area
|
||||
_context.appendJumpTo(copyLoopEndWithoutByteOffset);
|
||||
_context << longByteArray;
|
||||
// Store length (2*length+1)
|
||||
_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
|
||||
_context << u256(1) << Instruction::ADD;
|
||||
_context << Instruction::DUP6 << Instruction::SSTORE;
|
||||
}
|
||||
|
||||
// skip copying if source length is zero
|
||||
_context << Instruction::DUP3 << Instruction::ISZERO;
|
||||
_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset);
|
||||
|
||||
if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized())
|
||||
CompilerUtils(_context).computeHashStatic();
|
||||
// stack: target_ref target_data_end source_length target_data_pos source_data_pos
|
||||
_context << Instruction::SWAP2;
|
||||
utils.convertLengthToSize(_sourceType);
|
||||
_context << Instruction::DUP3 << Instruction::ADD;
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end
|
||||
if (haveByteOffsetTarget)
|
||||
_context << u256(0);
|
||||
if (haveByteOffsetSource)
|
||||
m_context << Instruction::DUP2;
|
||||
_context << u256(0);
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
|
||||
eth::AssemblyItem copyLoopStart = _context.newTag();
|
||||
_context << copyLoopStart;
|
||||
// check for loop condition
|
||||
_context
|
||||
<< dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize)
|
||||
<< Instruction::GT << Instruction::ISZERO;
|
||||
eth::AssemblyItem copyLoopEnd = _context.appendConditionalJump();
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
|
||||
// copy
|
||||
if (sourceBaseType->category() == Type::Category::Array)
|
||||
{
|
||||
solAssert(byteOffsetSize == 0, "Byte offset for array as base type.");
|
||||
auto const& sourceBaseArrayType = dynamic_cast<ArrayType const&>(*sourceBaseType);
|
||||
_context << Instruction::DUP3;
|
||||
if (sourceBaseArrayType.location() == DataLocation::Memory)
|
||||
_context << Instruction::MLOAD;
|
||||
_context << Instruction::DUP3;
|
||||
utils.copyArrayToStorage(dynamic_cast<ArrayType const&>(*targetBaseType), sourceBaseArrayType);
|
||||
_context << Instruction::POP;
|
||||
}
|
||||
else if (directCopy)
|
||||
{
|
||||
solAssert(byteOffsetSize == 0, "Byte offset for direct copy.");
|
||||
_context
|
||||
<< Instruction::DUP3 << Instruction::SLOAD
|
||||
<< Instruction::DUP3 << Instruction::SSTORE;
|
||||
}
|
||||
else
|
||||
m_context << u256(0);
|
||||
StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true);
|
||||
{
|
||||
// Note that we have to copy each element on its own in case conversion is involved.
|
||||
// We might copy too much if there is padding at the last element, but this way end
|
||||
// checking is easier.
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
|
||||
_context << dupInstruction(3 + byteOffsetSize);
|
||||
if (_sourceType.location() == DataLocation::Storage)
|
||||
{
|
||||
if (haveByteOffsetSource)
|
||||
_context << Instruction::DUP2;
|
||||
else
|
||||
_context << u256(0);
|
||||
StorageItem(_context, *sourceBaseType).retrieveValue(SourceLocation(), true);
|
||||
}
|
||||
else if (sourceBaseType->isValueType())
|
||||
CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
|
||||
else
|
||||
solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported.");
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>...
|
||||
solAssert(
|
||||
2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16,
|
||||
"Stack too deep, try removing local variables."
|
||||
);
|
||||
// fetch target storage reference
|
||||
_context << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack());
|
||||
if (haveByteOffsetTarget)
|
||||
_context << dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack());
|
||||
else
|
||||
_context << u256(0);
|
||||
StorageItem(_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true);
|
||||
}
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
|
||||
// increment source
|
||||
if (haveByteOffsetSource)
|
||||
utils.incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4);
|
||||
else
|
||||
{
|
||||
_context << swapInstruction(2 + byteOffsetSize);
|
||||
if (sourceIsStorage)
|
||||
_context << sourceBaseType->storageSize();
|
||||
else if (_sourceType.location() == DataLocation::Memory)
|
||||
_context << sourceBaseType->memoryHeadSize();
|
||||
else
|
||||
_context << sourceBaseType->calldataEncodedSize(true);
|
||||
_context
|
||||
<< Instruction::ADD
|
||||
<< swapInstruction(2 + byteOffsetSize);
|
||||
}
|
||||
// increment target
|
||||
if (haveByteOffsetTarget)
|
||||
utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
|
||||
else
|
||||
_context
|
||||
<< swapInstruction(1 + byteOffsetSize)
|
||||
<< targetBaseType->storageSize()
|
||||
<< Instruction::ADD
|
||||
<< swapInstruction(1 + byteOffsetSize);
|
||||
_context.appendJumpTo(copyLoopStart);
|
||||
_context << copyLoopEnd;
|
||||
if (haveByteOffsetTarget)
|
||||
{
|
||||
// clear elements that might be left over in the current slot in target
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset]
|
||||
_context << dupInstruction(byteOffsetSize) << Instruction::ISZERO;
|
||||
eth::AssemblyItem copyCleanupLoopEnd = _context.appendConditionalJump();
|
||||
_context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize);
|
||||
StorageItem(_context, *targetBaseType).setToZero(SourceLocation(), true);
|
||||
utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
|
||||
_context.appendJumpTo(copyLoopEnd);
|
||||
|
||||
_context << copyCleanupLoopEnd;
|
||||
_context << Instruction::POP; // might pop the source, but then target is popped next
|
||||
}
|
||||
if (haveByteOffsetSource)
|
||||
_context << Instruction::POP;
|
||||
_context << copyLoopEndWithoutByteOffset;
|
||||
|
||||
// zero-out leftovers in target
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end
|
||||
_context << Instruction::POP << Instruction::SWAP1 << Instruction::POP;
|
||||
// stack: target_ref target_data_end target_data_pos_updated
|
||||
utils.clearStorageLoop(targetBaseType);
|
||||
_context << Instruction::POP;
|
||||
}
|
||||
else if (sourceBaseType->isValueType())
|
||||
CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
|
||||
else
|
||||
solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported.");
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>...
|
||||
solAssert(
|
||||
2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16,
|
||||
"Stack too deep, try removing local variables."
|
||||
);
|
||||
// fetch target storage reference
|
||||
m_context << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack());
|
||||
if (haveByteOffsetTarget)
|
||||
m_context << dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack());
|
||||
else
|
||||
m_context << u256(0);
|
||||
StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true);
|
||||
}
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
|
||||
// increment source
|
||||
if (haveByteOffsetSource)
|
||||
incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4);
|
||||
else
|
||||
{
|
||||
m_context << swapInstruction(2 + byteOffsetSize);
|
||||
if (sourceIsStorage)
|
||||
m_context << sourceBaseType->storageSize();
|
||||
else if (_sourceType.location() == DataLocation::Memory)
|
||||
m_context << sourceBaseType->memoryHeadSize();
|
||||
else
|
||||
m_context << sourceBaseType->calldataEncodedSize(true);
|
||||
m_context
|
||||
<< Instruction::ADD
|
||||
<< swapInstruction(2 + byteOffsetSize);
|
||||
}
|
||||
// increment target
|
||||
if (haveByteOffsetTarget)
|
||||
incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
|
||||
else
|
||||
m_context
|
||||
<< swapInstruction(1 + byteOffsetSize)
|
||||
<< targetBaseType->storageSize()
|
||||
<< Instruction::ADD
|
||||
<< swapInstruction(1 + byteOffsetSize);
|
||||
m_context.appendJumpTo(copyLoopStart);
|
||||
m_context << copyLoopEnd;
|
||||
if (haveByteOffsetTarget)
|
||||
{
|
||||
// clear elements that might be left over in the current slot in target
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset]
|
||||
m_context << dupInstruction(byteOffsetSize) << Instruction::ISZERO;
|
||||
eth::AssemblyItem copyCleanupLoopEnd = m_context.appendConditionalJump();
|
||||
m_context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize);
|
||||
StorageItem(m_context, *targetBaseType).setToZero(SourceLocation(), true);
|
||||
incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
|
||||
m_context.appendJumpTo(copyLoopEnd);
|
||||
|
||||
m_context << copyCleanupLoopEnd;
|
||||
m_context << Instruction::POP; // might pop the source, but then target is popped next
|
||||
}
|
||||
if (haveByteOffsetSource)
|
||||
m_context << Instruction::POP;
|
||||
m_context << copyLoopEndWithoutByteOffset;
|
||||
|
||||
// zero-out leftovers in target
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end
|
||||
m_context << Instruction::POP << Instruction::SWAP1 << Instruction::POP;
|
||||
// stack: target_ref target_data_end target_data_pos_updated
|
||||
clearStorageLoop(*targetBaseType);
|
||||
m_context << Instruction::POP;
|
||||
);
|
||||
}
|
||||
|
||||
void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const
|
||||
@ -502,60 +516,70 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
|
||||
}
|
||||
}
|
||||
|
||||
void ArrayUtils::clearArray(ArrayType const& _type) const
|
||||
void ArrayUtils::clearArray(ArrayType const& _typeIn) const
|
||||
{
|
||||
unsigned stackHeightStart = m_context.stackHeight();
|
||||
solAssert(_type.location() == DataLocation::Storage, "");
|
||||
if (_type.baseType()->storageBytes() < 32)
|
||||
{
|
||||
solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
|
||||
solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type.");
|
||||
}
|
||||
if (_type.baseType()->isValueType())
|
||||
solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type.");
|
||||
|
||||
m_context << Instruction::POP; // remove byte offset
|
||||
if (_type.isDynamicallySized())
|
||||
clearDynamicArray(_type);
|
||||
else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping)
|
||||
m_context << Instruction::POP;
|
||||
else if (_type.baseType()->isValueType() && _type.storageSize() <= 5)
|
||||
{
|
||||
// unroll loop for small arrays @todo choose a good value
|
||||
// Note that we loop over storage slots here, not elements.
|
||||
for (unsigned i = 1; i < _type.storageSize(); ++i)
|
||||
m_context
|
||||
<< u256(0) << Instruction::DUP2 << Instruction::SSTORE
|
||||
<< u256(1) << Instruction::ADD;
|
||||
m_context << u256(0) << Instruction::SWAP1 << Instruction::SSTORE;
|
||||
}
|
||||
else if (!_type.baseType()->isValueType() && _type.length() <= 4)
|
||||
{
|
||||
// unroll loop for small arrays @todo choose a good value
|
||||
solAssert(_type.baseType()->storageBytes() >= 32, "Invalid storage size.");
|
||||
for (unsigned i = 1; i < _type.length(); ++i)
|
||||
TypePointer type = _typeIn.shared_from_this();
|
||||
m_context.callLowLevelFunction(
|
||||
"$clearArray_" + _typeIn.identifier(),
|
||||
2,
|
||||
0,
|
||||
[type](CompilerContext& _context)
|
||||
{
|
||||
m_context << u256(0);
|
||||
StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), false);
|
||||
m_context
|
||||
<< Instruction::POP
|
||||
<< u256(_type.baseType()->storageSize()) << Instruction::ADD;
|
||||
ArrayType const& _type = dynamic_cast<ArrayType const&>(*type);
|
||||
unsigned stackHeightStart = _context.stackHeight();
|
||||
solAssert(_type.location() == DataLocation::Storage, "");
|
||||
if (_type.baseType()->storageBytes() < 32)
|
||||
{
|
||||
solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
|
||||
solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type.");
|
||||
}
|
||||
if (_type.baseType()->isValueType())
|
||||
solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type.");
|
||||
|
||||
_context << Instruction::POP; // remove byte offset
|
||||
if (_type.isDynamicallySized())
|
||||
ArrayUtils(_context).clearDynamicArray(_type);
|
||||
else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping)
|
||||
_context << Instruction::POP;
|
||||
else if (_type.baseType()->isValueType() && _type.storageSize() <= 5)
|
||||
{
|
||||
// unroll loop for small arrays @todo choose a good value
|
||||
// Note that we loop over storage slots here, not elements.
|
||||
for (unsigned i = 1; i < _type.storageSize(); ++i)
|
||||
_context
|
||||
<< u256(0) << Instruction::DUP2 << Instruction::SSTORE
|
||||
<< u256(1) << Instruction::ADD;
|
||||
_context << u256(0) << Instruction::SWAP1 << Instruction::SSTORE;
|
||||
}
|
||||
else if (!_type.baseType()->isValueType() && _type.length() <= 4)
|
||||
{
|
||||
// unroll loop for small arrays @todo choose a good value
|
||||
solAssert(_type.baseType()->storageBytes() >= 32, "Invalid storage size.");
|
||||
for (unsigned i = 1; i < _type.length(); ++i)
|
||||
{
|
||||
_context << u256(0);
|
||||
StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), false);
|
||||
_context
|
||||
<< Instruction::POP
|
||||
<< u256(_type.baseType()->storageSize()) << Instruction::ADD;
|
||||
}
|
||||
_context << u256(0);
|
||||
StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_context << Instruction::DUP1 << _type.length();
|
||||
ArrayUtils(_context).convertLengthToSize(_type);
|
||||
_context << Instruction::ADD << Instruction::SWAP1;
|
||||
if (_type.baseType()->storageBytes() < 32)
|
||||
ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
|
||||
else
|
||||
ArrayUtils(_context).clearStorageLoop(_type.baseType());
|
||||
_context << Instruction::POP;
|
||||
}
|
||||
solAssert(_context.stackHeight() == stackHeightStart - 2, "");
|
||||
}
|
||||
m_context << u256(0);
|
||||
StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_context << Instruction::DUP1 << _type.length();
|
||||
convertLengthToSize(_type);
|
||||
m_context << Instruction::ADD << Instruction::SWAP1;
|
||||
if (_type.baseType()->storageBytes() < 32)
|
||||
clearStorageLoop(IntegerType(256));
|
||||
else
|
||||
clearStorageLoop(*_type.baseType());
|
||||
m_context << Instruction::POP;
|
||||
}
|
||||
solAssert(m_context.stackHeight() == stackHeightStart - 2, "");
|
||||
);
|
||||
}
|
||||
|
||||
void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
|
||||
@ -589,191 +613,209 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
|
||||
<< Instruction::SWAP1;
|
||||
// stack: data_pos_end data_pos
|
||||
if (_type.isByteArray() || _type.baseType()->storageBytes() < 32)
|
||||
clearStorageLoop(IntegerType(256));
|
||||
clearStorageLoop(make_shared<IntegerType>(256));
|
||||
else
|
||||
clearStorageLoop(*_type.baseType());
|
||||
clearStorageLoop(_type.baseType());
|
||||
// cleanup
|
||||
m_context << endTag;
|
||||
m_context << Instruction::POP;
|
||||
}
|
||||
|
||||
void ArrayUtils::resizeDynamicArray(ArrayType const& _type) const
|
||||
void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const
|
||||
{
|
||||
solAssert(_type.location() == DataLocation::Storage, "");
|
||||
solAssert(_type.isDynamicallySized(), "");
|
||||
if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32)
|
||||
solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
|
||||
TypePointer type = _typeIn.shared_from_this();
|
||||
m_context.callLowLevelFunction(
|
||||
"$resizeDynamicArray_" + _typeIn.identifier(),
|
||||
2,
|
||||
0,
|
||||
[type](CompilerContext& _context)
|
||||
{
|
||||
ArrayType const& _type = dynamic_cast<ArrayType const&>(*type);
|
||||
solAssert(_type.location() == DataLocation::Storage, "");
|
||||
solAssert(_type.isDynamicallySized(), "");
|
||||
if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32)
|
||||
solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
|
||||
|
||||
unsigned stackHeightStart = m_context.stackHeight();
|
||||
eth::AssemblyItem resizeEnd = m_context.newTag();
|
||||
unsigned stackHeightStart = _context.stackHeight();
|
||||
eth::AssemblyItem resizeEnd = _context.newTag();
|
||||
|
||||
// stack: ref new_length
|
||||
// fetch old length
|
||||
retrieveLength(_type, 1);
|
||||
// stack: ref new_length old_length
|
||||
solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "2");
|
||||
// stack: ref new_length
|
||||
// fetch old length
|
||||
ArrayUtils(_context).retrieveLength(_type, 1);
|
||||
// stack: ref new_length old_length
|
||||
solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "2");
|
||||
|
||||
// Special case for short byte arrays, they are stored together with their length
|
||||
if (_type.isByteArray())
|
||||
{
|
||||
eth::AssemblyItem regularPath = m_context.newTag();
|
||||
// We start by a large case-distinction about the old and new length of the byte array.
|
||||
// Special case for short byte arrays, they are stored together with their length
|
||||
if (_type.isByteArray())
|
||||
{
|
||||
eth::AssemblyItem regularPath = _context.newTag();
|
||||
// We start by a large case-distinction about the old and new length of the byte array.
|
||||
|
||||
m_context << Instruction::DUP3 << Instruction::SLOAD;
|
||||
// stack: ref new_length current_length ref_value
|
||||
_context << Instruction::DUP3 << Instruction::SLOAD;
|
||||
// stack: ref new_length current_length ref_value
|
||||
|
||||
solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3");
|
||||
m_context << Instruction::DUP2 << u256(31) << Instruction::LT;
|
||||
eth::AssemblyItem currentIsLong = m_context.appendConditionalJump();
|
||||
m_context << Instruction::DUP3 << u256(31) << Instruction::LT;
|
||||
eth::AssemblyItem newIsLong = m_context.appendConditionalJump();
|
||||
solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
|
||||
_context << Instruction::DUP2 << u256(31) << Instruction::LT;
|
||||
eth::AssemblyItem currentIsLong = _context.appendConditionalJump();
|
||||
_context << Instruction::DUP3 << u256(31) << Instruction::LT;
|
||||
eth::AssemblyItem newIsLong = _context.appendConditionalJump();
|
||||
|
||||
// Here: short -> short
|
||||
// Here: short -> short
|
||||
|
||||
// Compute 1 << (256 - 8 * new_size)
|
||||
eth::AssemblyItem shortToShort = m_context.newTag();
|
||||
m_context << shortToShort;
|
||||
m_context << Instruction::DUP3 << u256(8) << Instruction::MUL;
|
||||
m_context << u256(0x100) << Instruction::SUB;
|
||||
m_context << u256(2) << Instruction::EXP;
|
||||
// Divide and multiply by that value, clearing bits.
|
||||
m_context << Instruction::DUP1 << Instruction::SWAP2;
|
||||
m_context << Instruction::DIV << Instruction::MUL;
|
||||
// Insert 2*length.
|
||||
m_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
|
||||
m_context << Instruction::OR;
|
||||
// Store.
|
||||
m_context << Instruction::DUP4 << Instruction::SSTORE;
|
||||
solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "3");
|
||||
m_context.appendJumpTo(resizeEnd);
|
||||
// Compute 1 << (256 - 8 * new_size)
|
||||
eth::AssemblyItem shortToShort = _context.newTag();
|
||||
_context << shortToShort;
|
||||
_context << Instruction::DUP3 << u256(8) << Instruction::MUL;
|
||||
_context << u256(0x100) << Instruction::SUB;
|
||||
_context << u256(2) << Instruction::EXP;
|
||||
// Divide and multiply by that value, clearing bits.
|
||||
_context << Instruction::DUP1 << Instruction::SWAP2;
|
||||
_context << Instruction::DIV << Instruction::MUL;
|
||||
// Insert 2*length.
|
||||
_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
|
||||
_context << Instruction::OR;
|
||||
// Store.
|
||||
_context << Instruction::DUP4 << Instruction::SSTORE;
|
||||
solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3");
|
||||
_context.appendJumpTo(resizeEnd);
|
||||
|
||||
m_context.adjustStackOffset(1); // we have to do that because of the jumps
|
||||
// Here: short -> long
|
||||
_context.adjustStackOffset(1); // we have to do that because of the jumps
|
||||
// Here: short -> long
|
||||
|
||||
m_context << newIsLong;
|
||||
// stack: ref new_length current_length ref_value
|
||||
solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3");
|
||||
// Zero out lower-order byte.
|
||||
m_context << u256(0xff) << Instruction::NOT << Instruction::AND;
|
||||
// Store at data location.
|
||||
m_context << Instruction::DUP4;
|
||||
CompilerUtils(m_context).computeHashStatic();
|
||||
m_context << Instruction::SSTORE;
|
||||
// stack: ref new_length current_length
|
||||
// Store new length: Compule 2*length + 1 and store it.
|
||||
m_context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD;
|
||||
m_context << u256(1) << Instruction::ADD;
|
||||
// stack: ref new_length current_length 2*new_length+1
|
||||
m_context << Instruction::DUP4 << Instruction::SSTORE;
|
||||
solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "3");
|
||||
m_context.appendJumpTo(resizeEnd);
|
||||
_context << newIsLong;
|
||||
// stack: ref new_length current_length ref_value
|
||||
solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
|
||||
// Zero out lower-order byte.
|
||||
_context << u256(0xff) << Instruction::NOT << Instruction::AND;
|
||||
// Store at data location.
|
||||
_context << Instruction::DUP4;
|
||||
CompilerUtils(_context).computeHashStatic();
|
||||
_context << Instruction::SSTORE;
|
||||
// stack: ref new_length current_length
|
||||
// Store new length: Compule 2*length + 1 and store it.
|
||||
_context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD;
|
||||
_context << u256(1) << Instruction::ADD;
|
||||
// stack: ref new_length current_length 2*new_length+1
|
||||
_context << Instruction::DUP4 << Instruction::SSTORE;
|
||||
solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3");
|
||||
_context.appendJumpTo(resizeEnd);
|
||||
|
||||
m_context.adjustStackOffset(1); // we have to do that because of the jumps
|
||||
_context.adjustStackOffset(1); // we have to do that because of the jumps
|
||||
|
||||
m_context << currentIsLong;
|
||||
m_context << Instruction::DUP3 << u256(31) << Instruction::LT;
|
||||
m_context.appendConditionalJumpTo(regularPath);
|
||||
_context << currentIsLong;
|
||||
_context << Instruction::DUP3 << u256(31) << Instruction::LT;
|
||||
_context.appendConditionalJumpTo(regularPath);
|
||||
|
||||
// Here: long -> short
|
||||
// Read the first word of the data and store it on the stack. Clear the data location and
|
||||
// then jump to the short -> short case.
|
||||
// Here: long -> short
|
||||
// Read the first word of the data and store it on the stack. Clear the data location and
|
||||
// then jump to the short -> short case.
|
||||
|
||||
// stack: ref new_length current_length ref_value
|
||||
solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3");
|
||||
m_context << Instruction::POP << Instruction::DUP3;
|
||||
CompilerUtils(m_context).computeHashStatic();
|
||||
m_context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1;
|
||||
// stack: ref new_length current_length first_word data_location
|
||||
m_context << Instruction::DUP3;
|
||||
convertLengthToSize(_type);
|
||||
m_context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1;
|
||||
// stack: ref new_length current_length first_word data_location_end data_location
|
||||
clearStorageLoop(IntegerType(256));
|
||||
m_context << Instruction::POP;
|
||||
// stack: ref new_length current_length first_word
|
||||
solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3");
|
||||
m_context.appendJumpTo(shortToShort);
|
||||
// stack: ref new_length current_length ref_value
|
||||
solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
|
||||
_context << Instruction::POP << Instruction::DUP3;
|
||||
CompilerUtils(_context).computeHashStatic();
|
||||
_context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1;
|
||||
// stack: ref new_length current_length first_word data_location
|
||||
_context << Instruction::DUP3;
|
||||
ArrayUtils(_context).convertLengthToSize(_type);
|
||||
_context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1;
|
||||
// stack: ref new_length current_length first_word data_location_end data_location
|
||||
ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
|
||||
_context << Instruction::POP;
|
||||
// stack: ref new_length current_length first_word
|
||||
solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
|
||||
_context.appendJumpTo(shortToShort);
|
||||
|
||||
m_context << regularPath;
|
||||
// stack: ref new_length current_length ref_value
|
||||
m_context << Instruction::POP;
|
||||
}
|
||||
_context << regularPath;
|
||||
// stack: ref new_length current_length ref_value
|
||||
_context << Instruction::POP;
|
||||
}
|
||||
|
||||
// Change of length for a regular array (i.e. length at location, data at sha3(location)).
|
||||
// stack: ref new_length old_length
|
||||
// store new length
|
||||
m_context << Instruction::DUP2;
|
||||
if (_type.isByteArray())
|
||||
// For a "long" byte array, store length as 2*length+1
|
||||
m_context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD;
|
||||
m_context<< Instruction::DUP4 << Instruction::SSTORE;
|
||||
// skip if size is not reduced
|
||||
m_context << Instruction::DUP2 << Instruction::DUP2
|
||||
<< Instruction::ISZERO << Instruction::GT;
|
||||
m_context.appendConditionalJumpTo(resizeEnd);
|
||||
// Change of length for a regular array (i.e. length at location, data at sha3(location)).
|
||||
// stack: ref new_length old_length
|
||||
// store new length
|
||||
_context << Instruction::DUP2;
|
||||
if (_type.isByteArray())
|
||||
// For a "long" byte array, store length as 2*length+1
|
||||
_context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD;
|
||||
_context<< Instruction::DUP4 << Instruction::SSTORE;
|
||||
// skip if size is not reduced
|
||||
_context << Instruction::DUP2 << Instruction::DUP2
|
||||
<< Instruction::ISZERO << Instruction::GT;
|
||||
_context.appendConditionalJumpTo(resizeEnd);
|
||||
|
||||
// size reduced, clear the end of the array
|
||||
// stack: ref new_length old_length
|
||||
convertLengthToSize(_type);
|
||||
m_context << Instruction::DUP2;
|
||||
convertLengthToSize(_type);
|
||||
// stack: ref new_length old_size new_size
|
||||
// compute data positions
|
||||
m_context << Instruction::DUP4;
|
||||
CompilerUtils(m_context).computeHashStatic();
|
||||
// stack: ref new_length old_size new_size data_pos
|
||||
m_context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD;
|
||||
// stack: ref new_length data_pos new_size delete_end
|
||||
m_context << Instruction::SWAP2 << Instruction::ADD;
|
||||
// stack: ref new_length delete_end delete_start
|
||||
if (_type.isByteArray() || _type.baseType()->storageBytes() < 32)
|
||||
clearStorageLoop(IntegerType(256));
|
||||
else
|
||||
clearStorageLoop(*_type.baseType());
|
||||
// size reduced, clear the end of the array
|
||||
// stack: ref new_length old_length
|
||||
ArrayUtils(_context).convertLengthToSize(_type);
|
||||
_context << Instruction::DUP2;
|
||||
ArrayUtils(_context).convertLengthToSize(_type);
|
||||
// stack: ref new_length old_size new_size
|
||||
// compute data positions
|
||||
_context << Instruction::DUP4;
|
||||
CompilerUtils(_context).computeHashStatic();
|
||||
// stack: ref new_length old_size new_size data_pos
|
||||
_context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD;
|
||||
// stack: ref new_length data_pos new_size delete_end
|
||||
_context << Instruction::SWAP2 << Instruction::ADD;
|
||||
// stack: ref new_length delete_end delete_start
|
||||
if (_type.isByteArray() || _type.baseType()->storageBytes() < 32)
|
||||
ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
|
||||
else
|
||||
ArrayUtils(_context).clearStorageLoop(_type.baseType());
|
||||
|
||||
m_context << resizeEnd;
|
||||
// cleanup
|
||||
m_context << Instruction::POP << Instruction::POP << Instruction::POP;
|
||||
solAssert(m_context.stackHeight() == stackHeightStart - 2, "");
|
||||
_context << resizeEnd;
|
||||
// cleanup
|
||||
_context << Instruction::POP << Instruction::POP << Instruction::POP;
|
||||
solAssert(_context.stackHeight() == stackHeightStart - 2, "");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void ArrayUtils::clearStorageLoop(Type const& _type) const
|
||||
void ArrayUtils::clearStorageLoop(TypePointer const& _type) const
|
||||
{
|
||||
unsigned stackHeightStart = m_context.stackHeight();
|
||||
if (_type.category() == Type::Category::Mapping)
|
||||
{
|
||||
m_context << Instruction::POP;
|
||||
return;
|
||||
}
|
||||
// stack: end_pos pos
|
||||
m_context.callLowLevelFunction(
|
||||
"$clearStorageLoop_" + _type->identifier(),
|
||||
2,
|
||||
1,
|
||||
[_type](CompilerContext& _context)
|
||||
{
|
||||
unsigned stackHeightStart = _context.stackHeight();
|
||||
if (_type->category() == Type::Category::Mapping)
|
||||
{
|
||||
_context << Instruction::POP;
|
||||
return;
|
||||
}
|
||||
// stack: end_pos pos
|
||||
|
||||
// jump to and return from the loop to allow for duplicate code removal
|
||||
eth::AssemblyItem returnTag = m_context.pushNewTag();
|
||||
m_context << Instruction::SWAP2 << Instruction::SWAP1;
|
||||
// jump to and return from the loop to allow for duplicate code removal
|
||||
eth::AssemblyItem returnTag = _context.pushNewTag();
|
||||
_context << Instruction::SWAP2 << Instruction::SWAP1;
|
||||
|
||||
// stack: <return tag> end_pos pos
|
||||
eth::AssemblyItem loopStart = m_context.appendJumpToNew();
|
||||
m_context << loopStart;
|
||||
// check for loop condition
|
||||
m_context << Instruction::DUP1 << Instruction::DUP3
|
||||
<< Instruction::GT << Instruction::ISZERO;
|
||||
eth::AssemblyItem zeroLoopEnd = m_context.newTag();
|
||||
m_context.appendConditionalJumpTo(zeroLoopEnd);
|
||||
// delete
|
||||
m_context << u256(0);
|
||||
StorageItem(m_context, _type).setToZero(SourceLocation(), false);
|
||||
m_context << Instruction::POP;
|
||||
// increment
|
||||
m_context << _type.storageSize() << Instruction::ADD;
|
||||
m_context.appendJumpTo(loopStart);
|
||||
// cleanup
|
||||
m_context << zeroLoopEnd;
|
||||
m_context << Instruction::POP << Instruction::SWAP1;
|
||||
// "return"
|
||||
m_context << Instruction::JUMP;
|
||||
// stack: <return tag> end_pos pos
|
||||
eth::AssemblyItem loopStart = _context.appendJumpToNew();
|
||||
_context << loopStart;
|
||||
// check for loop condition
|
||||
_context << Instruction::DUP1 << Instruction::DUP3
|
||||
<< Instruction::GT << Instruction::ISZERO;
|
||||
eth::AssemblyItem zeroLoopEnd = _context.newTag();
|
||||
_context.appendConditionalJumpTo(zeroLoopEnd);
|
||||
// delete
|
||||
_context << u256(0);
|
||||
StorageItem(_context, *_type).setToZero(SourceLocation(), false);
|
||||
_context << Instruction::POP;
|
||||
// increment
|
||||
_context << _type->storageSize() << Instruction::ADD;
|
||||
_context.appendJumpTo(loopStart);
|
||||
// cleanup
|
||||
_context << zeroLoopEnd;
|
||||
_context << Instruction::POP << Instruction::SWAP1;
|
||||
// "return"
|
||||
_context << Instruction::JUMP;
|
||||
|
||||
m_context << returnTag;
|
||||
solAssert(m_context.stackHeight() == stackHeightStart - 1, "");
|
||||
_context << returnTag;
|
||||
solAssert(_context.stackHeight() == stackHeightStart - 1, "");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const
|
||||
@ -859,7 +901,7 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) c
|
||||
// check out-of-bounds access
|
||||
m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO;
|
||||
// out-of-bounds access throws exception
|
||||
m_context.appendConditionalJumpTo(m_context.errorTag());
|
||||
m_context.appendConditionalInvalid();
|
||||
}
|
||||
if (location == DataLocation::CallData && _arrayType.isDynamicallySized())
|
||||
// remove length if present
|
||||
|
||||
@ -22,6 +22,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
@ -30,6 +32,7 @@ namespace solidity
|
||||
class CompilerContext;
|
||||
class Type;
|
||||
class ArrayType;
|
||||
using TypePointer = std::shared_ptr<Type const>;
|
||||
|
||||
/**
|
||||
* Class that provides code generation for handling arrays.
|
||||
@ -67,7 +70,7 @@ public:
|
||||
/// Appends a loop that clears a sequence of storage slots of the given type (excluding end).
|
||||
/// Stack pre: end_ref start_ref
|
||||
/// Stack post: end_ref
|
||||
void clearStorageLoop(Type const& _type) const;
|
||||
void clearStorageLoop(TypePointer const& _type) const;
|
||||
/// Converts length to size (number of storage slots or calldata/memory bytes).
|
||||
/// if @a _pad then add padding to multiples of 32 bytes for calldata/memory.
|
||||
/// Stack pre: length
|
||||
|
||||
@ -21,15 +21,18 @@
|
||||
*/
|
||||
|
||||
#include <libsolidity/codegen/CompilerContext.h>
|
||||
#include <utility>
|
||||
#include <numeric>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <libsolidity/codegen/CompilerUtils.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/codegen/Compiler.h>
|
||||
#include <libsolidity/interface/Version.h>
|
||||
#include <libsolidity/inlineasm/AsmData.h>
|
||||
#include <libsolidity/inlineasm/AsmStack.h>
|
||||
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
||||
#include <utility>
|
||||
#include <numeric>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace dev
|
||||
@ -57,6 +60,62 @@ void CompilerContext::startFunction(Declaration const& _function)
|
||||
*this << functionEntryLabel(_function);
|
||||
}
|
||||
|
||||
void CompilerContext::callLowLevelFunction(
|
||||
string const& _name,
|
||||
unsigned _inArgs,
|
||||
unsigned _outArgs,
|
||||
function<void(CompilerContext&)> const& _generator
|
||||
)
|
||||
{
|
||||
eth::AssemblyItem retTag = pushNewTag();
|
||||
CompilerUtils(*this).moveIntoStack(_inArgs);
|
||||
|
||||
*this << lowLevelFunctionTag(_name, _inArgs, _outArgs, _generator);
|
||||
|
||||
appendJump(eth::AssemblyItem::JumpType::IntoFunction);
|
||||
adjustStackOffset(int(_outArgs) - 1 - _inArgs);
|
||||
*this << retTag.tag();
|
||||
}
|
||||
|
||||
eth::AssemblyItem CompilerContext::lowLevelFunctionTag(
|
||||
string const& _name,
|
||||
unsigned _inArgs,
|
||||
unsigned _outArgs,
|
||||
function<void(CompilerContext&)> const& _generator
|
||||
)
|
||||
{
|
||||
auto it = m_lowLevelFunctions.find(_name);
|
||||
if (it == m_lowLevelFunctions.end())
|
||||
{
|
||||
eth::AssemblyItem tag = newTag().pushTag();
|
||||
m_lowLevelFunctions.insert(make_pair(_name, tag));
|
||||
m_lowLevelFunctionGenerationQueue.push(make_tuple(_name, _inArgs, _outArgs, _generator));
|
||||
return tag;
|
||||
}
|
||||
else
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void CompilerContext::appendMissingLowLevelFunctions()
|
||||
{
|
||||
while (!m_lowLevelFunctionGenerationQueue.empty())
|
||||
{
|
||||
string name;
|
||||
unsigned inArgs;
|
||||
unsigned outArgs;
|
||||
function<void(CompilerContext&)> generator;
|
||||
tie(name, inArgs, outArgs, generator) = m_lowLevelFunctionGenerationQueue.front();
|
||||
m_lowLevelFunctionGenerationQueue.pop();
|
||||
|
||||
setStackOffset(inArgs + 1);
|
||||
*this << m_lowLevelFunctions.at(name).tag();
|
||||
generator(*this);
|
||||
CompilerUtils(*this).moveToStackTop(outArgs);
|
||||
appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
|
||||
solAssert(stackHeight() == outArgs, "Invalid stack height in low-level function " + name + ".");
|
||||
}
|
||||
}
|
||||
|
||||
void CompilerContext::addVariable(VariableDeclaration const& _declaration,
|
||||
unsigned _offsetToCurrent)
|
||||
{
|
||||
@ -167,6 +226,20 @@ CompilerContext& CompilerContext::appendJump(eth::AssemblyItem::JumpType _jumpTy
|
||||
return *this << item;
|
||||
}
|
||||
|
||||
CompilerContext& CompilerContext::appendInvalid()
|
||||
{
|
||||
return *this << Instruction::INVALID;
|
||||
}
|
||||
|
||||
CompilerContext& CompilerContext::appendConditionalInvalid()
|
||||
{
|
||||
*this << Instruction::ISZERO;
|
||||
eth::AssemblyItem afterTag = appendConditionalJump();
|
||||
*this << Instruction::INVALID;
|
||||
*this << afterTag;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void CompilerContext::resetVisitedNodes(ASTNode const* _node)
|
||||
{
|
||||
stack<ASTNode const*> newStack;
|
||||
|
||||
@ -22,16 +22,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/ast/ASTForward.h>
|
||||
#include <libsolidity/ast/Types.h>
|
||||
#include <libsolidity/ast/ASTAnnotations.h>
|
||||
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <libevmasm/Assembly.h>
|
||||
|
||||
#include <libdevcore/Common.h>
|
||||
|
||||
#include <ostream>
|
||||
#include <stack>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <libevmasm/Assembly.h>
|
||||
#include <libsolidity/ast/ASTForward.h>
|
||||
#include <libsolidity/ast/Types.h>
|
||||
#include <libsolidity/ast/ASTAnnotations.h>
|
||||
#include <libdevcore/Common.h>
|
||||
#include <functional>
|
||||
|
||||
namespace dev {
|
||||
namespace solidity {
|
||||
@ -90,6 +94,29 @@ public:
|
||||
/// as "having code".
|
||||
void startFunction(Declaration const& _function);
|
||||
|
||||
/// Appends a call to 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,
|
||||
/// unless they are guaranteed to be alive for the whole run of the compiler (AST nodes, for example).
|
||||
void callLowLevelFunction(
|
||||
std::string const& _name,
|
||||
unsigned _inArgs,
|
||||
unsigned _outArgs,
|
||||
std::function<void(CompilerContext&)> const& _generator
|
||||
);
|
||||
/// 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,
|
||||
/// unless they are guaranteed to be alive for the whole run of the compiler (AST nodes, for example).
|
||||
eth::AssemblyItem lowLevelFunctionTag(
|
||||
std::string const& _name,
|
||||
unsigned _inArgs,
|
||||
unsigned _outArgs,
|
||||
std::function<void(CompilerContext&)> const& _generator
|
||||
);
|
||||
/// Generates the code for missing low-level functions, i.e. calls the generators passed above.
|
||||
void appendMissingLowLevelFunctions();
|
||||
|
||||
ModifierDefinition const& functionModifier(std::string const& _name) const;
|
||||
/// Returns the distance of the given local variable from the bottom of the stack (of the current function).
|
||||
unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const;
|
||||
@ -110,6 +137,10 @@ public:
|
||||
eth::AssemblyItem appendJumpToNew() { return m_asm->appendJump().tag(); }
|
||||
/// Appends a JUMP to a tag already on the stack
|
||||
CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary);
|
||||
/// Appends an INVALID instruction
|
||||
CompilerContext& appendInvalid();
|
||||
/// Appends a conditional INVALID instruction
|
||||
CompilerContext& appendConditionalInvalid();
|
||||
/// Returns an "ErrorTag"
|
||||
eth::AssemblyItem errorTag() { return m_asm->errorTag(); }
|
||||
/// Appends a JUMP to a specific tag
|
||||
@ -248,6 +279,10 @@ private:
|
||||
CompilerContext *m_runtimeContext;
|
||||
/// The index of the runtime subroutine.
|
||||
size_t m_runtimeSub = -1;
|
||||
/// An index of low-level function labels by name.
|
||||
std::map<std::string, eth::AssemblyItem> m_lowLevelFunctions;
|
||||
/// The queue of low-level functions to generate.
|
||||
std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@ -200,6 +200,7 @@ void CompilerUtils::encodeToMemory(
|
||||
// leave end_of_mem as dyn head pointer
|
||||
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
|
||||
dynPointers++;
|
||||
solAssert((argSize + dynPointers) < 16, "Stack too deep, try using less variables.");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -468,7 +469,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
|
||||
EnumType const& enumType = dynamic_cast<decltype(enumType)>(_typeOnStack);
|
||||
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
|
||||
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
|
||||
m_context.appendConditionalJumpTo(m_context.errorTag());
|
||||
m_context.appendConditionalInvalid();
|
||||
enumOverflowCheckPending = false;
|
||||
}
|
||||
break;
|
||||
@ -497,7 +498,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
|
||||
EnumType const& enumType = dynamic_cast<decltype(enumType)>(_targetType);
|
||||
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
|
||||
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
|
||||
m_context.appendConditionalJumpTo(m_context.errorTag());
|
||||
m_context.appendConditionalInvalid();
|
||||
enumOverflowCheckPending = false;
|
||||
}
|
||||
else if (targetTypeCategory == Type::Category::FixedPoint)
|
||||
@ -787,6 +788,20 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
|
||||
if (_cleanupNeeded)
|
||||
m_context << Instruction::ISZERO << Instruction::ISZERO;
|
||||
break;
|
||||
case Type::Category::Function:
|
||||
{
|
||||
if (targetTypeCategory == Type::Category::Integer)
|
||||
{
|
||||
IntegerType const& targetType = dynamic_cast<IntegerType const&>(_targetType);
|
||||
solAssert(targetType.isAddress(), "Function type can only be converted to address.");
|
||||
FunctionType const& typeOnStack = dynamic_cast<FunctionType const&>(_typeOnStack);
|
||||
solAssert(typeOnStack.location() == FunctionType::Location::External, "Only external function type can be converted.");
|
||||
|
||||
// stack: <address> <function_id>
|
||||
m_context << Instruction::POP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
default:
|
||||
// All other types should not be convertible to non-equal types.
|
||||
solAssert(_typeOnStack == _targetType, "Invalid type conversion requested.");
|
||||
@ -807,7 +822,9 @@ void CompilerUtils::pushZeroValue(Type const& _type)
|
||||
{
|
||||
if (funType->location() == FunctionType::Location::Internal)
|
||||
{
|
||||
m_context << m_context.errorTag();
|
||||
m_context << m_context.lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) {
|
||||
_context.appendInvalid();
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -820,37 +837,46 @@ void CompilerUtils::pushZeroValue(Type const& _type)
|
||||
}
|
||||
solAssert(referenceType->location() == DataLocation::Memory, "");
|
||||
|
||||
m_context << u256(max(32u, _type.calldataEncodedSize()));
|
||||
allocateMemory();
|
||||
m_context << Instruction::DUP1;
|
||||
TypePointer type = _type.shared_from_this();
|
||||
m_context.callLowLevelFunction(
|
||||
"$pushZeroValue_" + referenceType->identifier(),
|
||||
0,
|
||||
1,
|
||||
[type](CompilerContext& _context) {
|
||||
CompilerUtils utils(_context);
|
||||
_context << u256(max(32u, type->calldataEncodedSize()));
|
||||
utils.allocateMemory();
|
||||
_context << Instruction::DUP1;
|
||||
|
||||
if (auto structType = dynamic_cast<StructType const*>(&_type))
|
||||
for (auto const& member: structType->members(nullptr))
|
||||
{
|
||||
pushZeroValue(*member.type);
|
||||
storeInMemoryDynamic(*member.type);
|
||||
}
|
||||
else if (auto arrayType = dynamic_cast<ArrayType const*>(&_type))
|
||||
{
|
||||
if (arrayType->isDynamicallySized())
|
||||
{
|
||||
// zero length
|
||||
m_context << u256(0);
|
||||
storeInMemoryDynamic(IntegerType(256));
|
||||
}
|
||||
else if (arrayType->length() > 0)
|
||||
{
|
||||
m_context << arrayType->length() << Instruction::SWAP1;
|
||||
// stack: items_to_do memory_pos
|
||||
zeroInitialiseMemoryArray(*arrayType);
|
||||
// stack: updated_memory_pos
|
||||
}
|
||||
}
|
||||
else
|
||||
solAssert(false, "Requested initialisation for unknown type: " + _type.toString());
|
||||
if (auto structType = dynamic_cast<StructType const*>(type.get()))
|
||||
for (auto const& member: structType->members(nullptr))
|
||||
{
|
||||
utils.pushZeroValue(*member.type);
|
||||
utils.storeInMemoryDynamic(*member.type);
|
||||
}
|
||||
else if (auto arrayType = dynamic_cast<ArrayType const*>(type.get()))
|
||||
{
|
||||
if (arrayType->isDynamicallySized())
|
||||
{
|
||||
// zero length
|
||||
_context << u256(0);
|
||||
utils.storeInMemoryDynamic(IntegerType(256));
|
||||
}
|
||||
else if (arrayType->length() > 0)
|
||||
{
|
||||
_context << arrayType->length() << Instruction::SWAP1;
|
||||
// stack: items_to_do memory_pos
|
||||
utils.zeroInitialiseMemoryArray(*arrayType);
|
||||
// stack: updated_memory_pos
|
||||
}
|
||||
}
|
||||
else
|
||||
solAssert(false, "Requested initialisation for unknown type: " + type->toString());
|
||||
|
||||
// remove the updated memory pointer
|
||||
m_context << Instruction::POP;
|
||||
// remove the updated memory pointer
|
||||
_context << Instruction::POP;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user