Merge pull request #2510 from ethereum/develop

Version 0.4.12
This commit is contained in:
chriseth 2017-07-03 14:52:29 +02:00 committed by GitHub
commit 76d3b7c5a1
177 changed files with 8593 additions and 4451 deletions

18
.editorconfig Normal file
View File

@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{cpp,h}]
indent_style = tab
[*.{py,rst,sh,yml}]
indent_style = space
indent_size = 4
[std/**.sol]
indent_style = space
indent_size = 4

1
.gitignore vendored
View File

@ -41,3 +41,4 @@ docs/utils/__pycache__
# IDE files
.idea
browse.VC.db
CMakeLists.txt.user

View File

@ -191,6 +191,7 @@ before_script:
&& scripts/create_source_tarball.sh)
script:
- test $SOLC_EMSCRIPTEN != On || (scripts/test_emscripten.sh)
- test $SOLC_DOCS != On || (scripts/docs.sh)
- test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && scripts/tests.sh)
- test $SOLC_STOREBYTECODE != On || (cd $TRAVIS_BUILD_DIR && scripts/bytecodecompare/storebytecode.sh)

View File

@ -8,7 +8,7 @@ include(EthPolicy)
eth_policy()
# project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.4.11")
set(PROJECT_VERSION "0.4.12")
project(solidity VERSION ${PROJECT_VERSION})
# Let's find our dependencies
@ -24,6 +24,15 @@ include(EthExecutableHelper)
# Include utils
include(EthUtils)
# Create license.h from LICENSE.txt and template
# Converting to char array is required due to MSVC's string size limit.
file(READ ${CMAKE_SOURCE_DIR}/LICENSE.txt LICENSE_TEXT HEX)
string(REGEX MATCHALL ".." LICENSE_TEXT "${LICENSE_TEXT}")
string(REGEX REPLACE ";" ",\n\t0x" LICENSE_TEXT "${LICENSE_TEXT}")
set(LICENSE_TEXT "0x${LICENSE_TEXT}")
configure_file("${CMAKE_SOURCE_DIR}/cmake/templates/license.h.in" "license.h")
include(EthOptions)
configure_project(TESTS)

View File

@ -1,3 +1,41 @@
### 0.4.12 (2017-07-03)
Features:
* Assembly: Add ``CREATE2`` (EIP86), ``STATICCALL`` (EIP214), ``RETURNDATASIZE`` and ``RETURNDATACOPY`` (EIP211) instructions.
* Assembly: Display auxiliary data in the assembly output.
* Assembly: Renamed ``SHA3`` to ``KECCAK256``.
* AST: export all attributes to JSON format.
* C API (``jsonCompiler``): Use the Standard JSON I/O internally.
* Code Generator: Added the Whiskers template system.
* Inline Assembly: ``for`` and ``switch`` statements.
* Inline Assembly: Function definitions and function calls.
* Inline Assembly: Introduce ``keccak256`` as an opcode. ``sha3`` is still a valid alias.
* Inline Assembly: Present proper error message when not supplying enough arguments to a functional
instruction.
* Inline Assembly: Warn when instructions shadow Solidity variables.
* Inline Assembly: Warn when using ``jump``s.
* Remove obsolete Why3 output.
* Type Checker: Enforce strict UTF-8 validation.
* Type Checker: Warn about copies in storage that might overwrite unexpectedly.
* Type Checker: Warn about type inference from literal numbers.
* Static Analyzer: Warn about deprecation of ``callcode``.
Bugfixes:
* Assembly: mark ``MLOAD`` to have side effects in the optimiser.
* Code Generator: Fix ABI encoding of empty literal string.
* Code Generator: Fix negative stack size checks.
* Code generator: Use ``REVERT`` instead of ``INVALID`` for generated input validation routines.
* Inline Assembly: Enforce function arguments when parsing functional instructions.
* Optimizer: Disallow optimizations involving ``MLOAD`` because it changes ``MSIZE``.
* Static Analyzer: Unused variable warnings no longer issued for variables used inside inline assembly.
* Type Checker: Fix address literals not being treated as compile-time constants.
* Type Checker: Fixed crash concerning non-callable types.
* Type Checker: Fixed segfault with constant function parameters
* Type Checker: Disallow comparisons between mapping and non-internal function types.
* Type Checker: Disallow invoking the same modifier multiple times.
* Type Checker: Do not treat strings that look like addresses as addresses.
* Type Checker: Support valid, but incorrectly rejected UTF-8 sequences.
### 0.4.11 (2017-05-03)
Features:

View File

@ -2,7 +2,7 @@
[![Join the chat at https://gitter.im/ethereum/solidity](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ethereum/solidity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Useful links
To get started you can find an introduction to the language in the [Solidity documentation](https://solidity.readthedocs.org). In the documentation, you can find [code examples](http://solidity.readthedocs.io/en/latest/solidity-by-example.html) as well as [a reference](http://solidity.readthedocs.io/en/latest/solidity-in-depth.html) of the syntax and details on how to write smart contracts.
To get started you can find an introduction to the language in the [Solidity documentation](https://solidity.readthedocs.org). In the documentation, you can find [code examples](https://solidity.readthedocs.io/en/latest/solidity-by-example.html) as well as [a reference](https://solidity.readthedocs.io/en/latest/solidity-in-depth.html) of the syntax and details on how to write smart contracts.
You can start using [Solidity in your browser](https://ethereum.github.io/browser-solidity/) with no need to download or compile anything.
@ -11,9 +11,9 @@ The changelog for this project can be found [here](https://github.com/ethereum/s
Solidity is still under development. So please do not hesitate and open an [issue in GitHub](https://github.com/ethereum/solidity/issues) if you encounter anything strange.
## Building
See the [Solidity documentation](http://solidity.readthedocs.io/en/latest/installing-solidity.html#building-from-source) for build instructions.
See the [Solidity documentation](https://solidity.readthedocs.io/en/latest/installing-solidity.html#building-from-source) for build instructions.
## How to Contribute
Please see our contribution guidelines in [the Solidity documentation](http://solidity.readthedocs.io/en/latest/contributing.html).
Please see our contribution guidelines in [the Solidity documentation](https://solidity.readthedocs.io/en/latest/contributing.html).
Any contributions are welcome!

View File

@ -65,7 +65,8 @@ build_script:
- msbuild solidity.sln /p:Configuration=%CONFIGURATION% /m:%NUMBER_OF_PROCESSORS% /v:minimal
- cd %APPVEYOR_BUILD_FOLDER%
- scripts\release.bat %CONFIGURATION%
- scripts\bytecodecompare\storebytecode.bat %CONFIGURATION% %APPVEYOR_REPO_COMMIT%
- ps: $bytecodedir = git show -s --format="%cd-%H" --date=short
- ps: scripts\bytecodecompare\storebytecode.bat $Env:CONFIGURATION $bytecodedir
test_script:
- cd %APPVEYOR_BUILD_FOLDER%

View File

@ -65,7 +65,7 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA
# Build everything as shared libraries (.so files)
add_definitions(-DSHAREDLIB)
# If supported for the target machine, emit position-independent code, suitable for dynamic
# linking and avoiding any limit on the size of the global offset table.
add_compile_options(-fPIC)
@ -94,6 +94,12 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA
add_compile_options(-fstack-protector)
endif()
# Until https://github.com/ethereum/solidity/issues/2479 is handled
# disable all implicit fallthrough warnings in the codebase for GCC > 7.0
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.0)
add_compile_options(-Wno-implicit-fallthrough)
endif()
# Additional Clang-specific compiler settings.
elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")

View File

@ -10,6 +10,7 @@ function(eth_apply TARGET REQUIRED SUBMODULE)
target_link_libraries(${TARGET} ${Boost_RANDOM_LIBRARIES})
target_link_libraries(${TARGET} ${Boost_FILESYSTEM_LIBRARIES})
target_link_libraries(${TARGET} ${Boost_SYSTEM_LIBRARIES})
target_link_libraries(${TARGET} ${Boost_REGEX_LIBRARIES})
if (DEFINED MSVC)
target_link_libraries(${TARGET} ${Boost_CHRONO_LIBRARIES})

View File

@ -0,0 +1,75 @@
#pragma once
#include <string>
static std::string const otherLicenses{R"(Most of the code is licensed under GPLv3 (see below), the license for individual
parts are as follows:
libkeccak-tiny:
A single-file implementation of SHA-3 and SHAKE implemented by David Leon Gil
License: CC0, attribution kindly requested. Blame taken too, but not liability.
jsoncpp:
The JsonCpp library's source code, including accompanying documentation,
tests and demonstration applications, are licensed under the following
conditions...
The JsonCpp Authors explicitly disclaim copyright in all
jurisdictions which recognize such a disclaimer. In such jurisdictions,
this software is released into the Public Domain.
In jurisdictions which do not recognize Public Domain property (e.g. Germany as of
2010), this software is Copyright (c) 2007-2010 by The JsonCpp Authors, and is
released under the terms of the MIT License (see below).
In jurisdictions which recognize Public Domain property, the user of this
software may choose to accept it either as 1) Public Domain, 2) under the
conditions of the MIT License (see below), or 3) under the terms of dual
Public Domain/MIT License conditions described here, as they choose.
The MIT License is about as close to Public Domain as a license can get, and is
described in clear, concise terms at:
http://en.wikipedia.org/wiki/MIT_License
The full text of the MIT License follows:
========================================================================
Copyright (c) 2007-2010 The JsonCpp Authors
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
========================================================================
(END LICENSE TEXT)
The MIT license is compatible with both the GPL and commercial
software, affording one all of the rights of Public Domain with the
minor nuisance of being required to keep the above copyright notice
and license text in the source code. Note also that by accepting the
Public Domain "license" you can re-license your copy using whatever
license you like.
All other code is licensed under GPL version 3:
)"};
static char const licenseText[] = {
@LICENSE_TEXT@, 0
};

2
deps

@ -1 +1 @@
Subproject commit b3db8905894eafb74a436b702de78ba235f3a3b1
Subproject commit e5c8316db8d3daa0abc3b5af8545ce330057608c

345
docs/abi-spec.rst Normal file
View File

@ -0,0 +1,345 @@
.. index:: abi, application binary interface
.. _ABI:
******************************************
Application Binary Interface Specification
******************************************
Basic design
============
The Application Binary Interface is the standard way to interact with contracts in the Ethereum ecosystem, both
from outside the blockchain and for contract-to-contract interaction. Data is encoded following its type,
according to this specification.
We assume the Application Binary Interface (ABI) is strongly typed, known at compilation time and static. No introspection mechanism will be provided. We assert that all contracts will have the interface definitions of any contracts they call available at compile-time.
This specification does not address contracts whose interface is dynamic or otherwise known only at run-time. Should these cases become important they can be adequately handled as facilities built within the Ethereum ecosystem.
Function Selector
=================
The first four bytes of the call data for a function call specifies the function to be called. It is the
first (left, high-order in big-endian) four bytes of the Keccak (SHA-3) hash of the signature of the function. The signature is defined as the canonical expression of the basic prototype, i.e.
the function name with the parenthesised list of parameter types. Parameter types are split by a single comma - no spaces are used.
Argument Encoding
=================
Starting from the fifth byte, the encoded arguments follow. This encoding is also used in other places, e.g. the return values and also event arguments are encoded in the same way, without the four bytes specifying the function.
Types
=====
The following elementary types exist:
- `uint<M>`: unsigned integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0`. e.g. `uint32`, `uint8`, `uint256`.
- `int<M>`: two's complement signed integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0`.
- `address`: equivalent to `uint160`, except for the assumed interpretation and language typing.
- `uint`, `int`: synonyms for `uint256`, `int256` respectively (not to be used for computing the function selector).
- `bool`: equivalent to `uint8` restricted to the values 0 and 1
- `fixed<M>x<N>`: signed fixed-point decimal number of `M` bits, `0 < M <= 256`, `M % 8 ==0`, and `0 < N <= 80`, which denotes the value `v` as `v / (10 ** N)`.
- `ufixed<M>x<N>`: unsigned variant of `fixed<M>x<N>`.
- `fixed`, `ufixed`: synonyms for `fixed128x19`, `ufixed128x19` respectively (not to be used for computing the function selector).
- `bytes<M>`: binary type of `M` bytes, `0 < M <= 32`.
- `function`: equivalent to `bytes24`: an address, followed by a function selector
The following (fixed-size) array type exists:
- `<type>[M]`: a fixed-length array of the given fixed-length type.
The following non-fixed-size types exist:
- `bytes`: dynamic sized byte sequence.
- `string`: dynamic sized unicode string assumed to be UTF-8 encoded.
- `<type>[]`: a variable-length array of the given fixed-length type.
Types can be combined to anonymous structs by enclosing a finite non-negative number
of them inside parentheses, separated by commas:
- `(T1,T2,...,Tn)`: anonymous struct (ordered tuple) consisting of the types `T1`, ..., `Tn`, `n >= 0`
It is possible to form structs of structs, arrays of structs and so on.
Formal Specification of the Encoding
====================================
We will now formally specify the encoding, such that it will have the following
properties, which are especially useful if some arguments are nested arrays:
Properties:
1. The number of reads necessary to access a value is at most the depth of the value inside the argument array structure, i.e. four reads are needed to retrieve `a_i[k][l][r]`. In a previous version of the ABI, the number of reads scaled linearly with the total number of dynamic parameters in the worst case.
2. The data of a variable or array element is not interleaved with other data and it is relocatable, i.e. it only uses relative "addresses"
We distinguish static and dynamic types. Static types are encoded in-place and dynamic types are encoded at a separately allocated location after the current block.
**Definition:** The following types are called "dynamic":
* `bytes`
* `string`
* `T[]` for any `T`
* `T[k]` for any dynamic `T` and any `k > 0`
All other types are called "static".
**Definition:** `len(a)` is the number of bytes in a binary string `a`.
The type of `len(a)` is assumed to be `uint256`.
We define `enc`, the actual encoding, as a mapping of values of the ABI types to binary strings such
that `len(enc(X))` depends on the value of `X` if and only if the type of `X` is dynamic.
**Definition:** For any ABI value `X`, we recursively define `enc(X)`, depending
on the type of `X` being
- `(T1,...,Tk)` for `k >= 0` and any types `T1`, ..., `Tk`
`enc(X) = head(X(1)) ... head(X(k-1)) tail(X(0)) ... tail(X(k-1))`
where `X(i)` is the `ith` component of the value, and
`head` and `tail` are defined for `Ti` being a static type as
`head(X(i)) = enc(X(i))` and `tail(X(i)) = ""` (the empty string)
and as
`head(X(i)) = enc(len(head(X(0)) ... head(X(k-1)) tail(X(0)) ... tail(X(i-1))))`
`tail(X(i)) = enc(X(i))`
otherwise, i.e. if `Ti` is a dynamic type.
Note that in the dynamic case, `head(X(i))` is well-defined since the lengths of
the head parts only depend on the types and not the values. Its value is the offset
of the beginning of `tail(X(i))` relative to the start of `enc(X)`.
- `T[k]` for any `T` and `k`:
`enc(X) = enc((X[0], ..., X[k-1]))`
i.e. it is encoded as if it were an anonymous struct with `k` elements
of the same type.
- `T[]` where `X` has `k` elements (`k` is assumed to be of type `uint256`):
`enc(X) = enc(k) enc([X[1], ..., X[k]])`
i.e. it is encoded as if it were an array of static size `k`, prefixed with
the number of elements.
- `bytes`, of length `k` (which is assumed to be of type `uint256`):
`enc(X) = enc(k) pad_right(X)`, i.e. the number of bytes is encoded as a
`uint256` followed by the actual value of `X` as a byte sequence, followed by
the minimum number of zero-bytes such that `len(enc(X))` is a multiple of 32.
- `string`:
`enc(X) = enc(enc_utf8(X))`, i.e. `X` is utf-8 encoded and this value is interpreted as of `bytes` type and encoded further. Note that the length used in this subsequent encoding is the number of bytes of the utf-8 encoded string, not its number of characters.
- `uint<M>`: `enc(X)` is the big-endian encoding of `X`, padded on the higher-order (left) side with zero-bytes such that the length is a multiple of 32 bytes.
- `address`: as in the `uint160` case
- `int<M>`: `enc(X)` is the big-endian two's complement encoding of `X`, padded on the higher-oder (left) side with `0xff` for negative `X` and with zero bytes for positive `X` such that the length is a multiple of 32 bytes.
- `bool`: as in the `uint8` case, where `1` is used for `true` and `0` for `false`
- `fixed<M>x<N>`: `enc(X)` is `enc(X * 10**N)` where `X * 10**N` is interpreted as a `int256`.
- `fixed`: as in the `fixed128x19` case
- `ufixed<M>x<N>`: `enc(X)` is `enc(X * 10**N)` where `X * 10**N` is interpreted as a `uint256`.
- `ufixed`: as in the `ufixed128x19` case
- `bytes<M>`: `enc(X)` is the sequence of bytes in `X` padded with zero-bytes to a length of 32.
Note that for any `X`, `len(enc(X))` is a multiple of 32.
Function Selector and Argument Encoding
=======================================
All in all, a call to the function `f` with parameters `a_1, ..., a_n` is encoded as
`function_selector(f) enc((a_1, ..., a_n))`
and the return values `v_1, ..., v_k` of `f` are encoded as
`enc((v_1, ..., v_k))`
i.e. the values are combined into an anonymous struct and encoded.
Examples
========
Given the contract:
::
contract Foo {
function bar(bytes3[2] xy) {}
function baz(uint32 x, bool y) returns (bool r) { r = x > 32 || y; }
function sam(bytes name, bool z, uint[] data) {}
}
Thus for our `Foo` example if we wanted to call `baz` with the parameters `69` and `true`, we would pass 68 bytes total, which can be broken down into:
- `0xcdcd77c0`: the Method ID. This is derived as the first 4 bytes of the Keccak hash of the ASCII form of the signature `baz(uint32,bool)`.
- `0x0000000000000000000000000000000000000000000000000000000000000045`: the first parameter, a uint32 value `69` padded to 32 bytes
- `0x0000000000000000000000000000000000000000000000000000000000000001`: the second parameter - boolean `true`, padded to 32 bytes
In total::
0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001
It returns a single `bool`. If, for example, it were to return `false`, its output would be the single byte array `0x0000000000000000000000000000000000000000000000000000000000000000`, a single bool.
If we wanted to call `bar` with the argument `["abc", "def"]`, we would pass 68 bytes total, broken down into:
- `0xfce353f6`: the Method ID. This is derived from the signature `bar(bytes3[2])`.
- `0x6162630000000000000000000000000000000000000000000000000000000000`: the first part of the first parameter, a `bytes3` value `"abc"` (left-aligned).
- `0x6465660000000000000000000000000000000000000000000000000000000000`: the second part of the first parameter, a `bytes3` value `"def"` (left-aligned).
In total::
0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000
If we wanted to call `sam` with the arguments `"dave"`, `true` and `[1,2,3]`, we would pass 292 bytes total, broken down into:
- `0xa5643bf2`: the Method ID. This is derived from the signature `sam(bytes,bool,uint256[])`. Note that `uint` is replaced with its canonical representation `uint256`.
- `0x0000000000000000000000000000000000000000000000000000000000000060`: the location of the data part of the first parameter (dynamic type), measured in bytes from the start of the arguments block. In this case, `0x60`.
- `0x0000000000000000000000000000000000000000000000000000000000000001`: the second parameter: boolean true.
- `0x00000000000000000000000000000000000000000000000000000000000000a0`: the location of the data part of the third parameter (dynamic type), measured in bytes. In this case, `0xa0`.
- `0x0000000000000000000000000000000000000000000000000000000000000004`: the data part of the first argument, it starts with the length of the byte array in elements, in this case, 4.
- `0x6461766500000000000000000000000000000000000000000000000000000000`: the contents of the first argument: the UTF-8 (equal to ASCII in this case) encoding of `"dave"`, padded on the right to 32 bytes.
- `0x0000000000000000000000000000000000000000000000000000000000000003`: the data part of the third argument, it starts with the length of the array in elements, in this case, 3.
- `0x0000000000000000000000000000000000000000000000000000000000000001`: the first entry of the third parameter.
- `0x0000000000000000000000000000000000000000000000000000000000000002`: the second entry of the third parameter.
- `0x0000000000000000000000000000000000000000000000000000000000000003`: the third entry of the third parameter.
In total::
0xa5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003
Use of Dynamic Types
====================
A call to a function with the signature `f(uint,uint32[],bytes10,bytes)` with values `(0x123, [0x456, 0x789], "1234567890", "Hello, world!")` is encoded in the following way:
We take the first four bytes of `sha3("f(uint256,uint32[],bytes10,bytes)")`, i.e. `0x8be65246`.
Then we encode the head parts of all four arguments. For the static types `uint256` and `bytes10`, these are directly the values we want to pass, whereas for the dynamic types `uint32[]` and `bytes`, we use the offset in bytes to the start of their data area, measured from the start of the value encoding (i.e. not counting the first four bytes containing the hash of the function signature). These are:
- `0x0000000000000000000000000000000000000000000000000000000000000123` (`0x123` padded to 32 bytes)
- `0x0000000000000000000000000000000000000000000000000000000000000080` (offset to start of data part of second parameter, 4*32 bytes, exactly the size of the head part)
- `0x3132333435363738393000000000000000000000000000000000000000000000` (`"1234567890"` padded to 32 bytes on the right)
- `0x00000000000000000000000000000000000000000000000000000000000000e0` (offset to start of data part of fourth parameter = offset to start of data part of first dynamic parameter + size of data part of first dynamic parameter = 4\*32 + 3\*32 (see below))
After this, the data part of the first dynamic argument, `[0x456, 0x789]` follows:
- `0x0000000000000000000000000000000000000000000000000000000000000002` (number of elements of the array, 2)
- `0x0000000000000000000000000000000000000000000000000000000000000456` (first element)
- `0x0000000000000000000000000000000000000000000000000000000000000789` (second element)
Finally, we encode the data part of the second dynamic argument, `"Hello, world!"`:
- `0x000000000000000000000000000000000000000000000000000000000000000d` (number of elements (bytes in this case): 13)
- `0x48656c6c6f2c20776f726c642100000000000000000000000000000000000000` (`"Hello, world!"` padded to 32 bytes on the right)
All together, the encoding is (newline after function selector and each 32-bytes for clarity):
::
0x8be65246
0000000000000000000000000000000000000000000000000000000000000123
0000000000000000000000000000000000000000000000000000000000000080
3132333435363738393000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000e0
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000456
0000000000000000000000000000000000000000000000000000000000000789
000000000000000000000000000000000000000000000000000000000000000d
48656c6c6f2c20776f726c642100000000000000000000000000000000000000
Events
======
Events are an abstraction of the Ethereum logging/event-watching protocol. Log entries provide the contract's address, a series of up to four topics and some arbitrary length binary data. Events leverage the existing function ABI in order to interpret this (together with an interface spec) as a properly typed structure.
Given an event name and series of event parameters, we split them into two sub-series: those which are indexed and those which are not. Those which are indexed, which may number up to 3, are used alongside the Keccak hash of the event signature to form the topics of the log entry. Those which as not indexed form the byte array of the event.
In effect, a log entry using this ABI is described as:
- `address`: the address of the contract (intrinsically provided by Ethereum);
- `topics[0]`: `keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")")` (`canonical_type_of` is a function that simply returns the canonical type of a given argument, e.g. for `uint indexed foo`, it would return `uint256`). If the event is declared as `anonymous` the `topics[0]` is not generated;
- `topics[n]`: `EVENT_INDEXED_ARGS[n - 1]` (`EVENT_INDEXED_ARGS` is the series of `EVENT_ARGS` that are indexed);
- `data`: `abi_serialise(EVENT_NON_INDEXED_ARGS)` (`EVENT_NON_INDEXED_ARGS` is the series of `EVENT_ARGS` that are not indexed, `abi_serialise` is the ABI serialisation function used for returning a series of typed values from a function, as described above).
JSON
====
The JSON format for a contract's interface is given by an array of function and/or event descriptions. A function description is a JSON object with the fields:
- `type`: `"function"`, `"constructor"`, or `"fallback"` (the :ref:`unnamed "default" function <fallback-function>`);
- `name`: the name of the function;
- `inputs`: an array of objects, each of which contains:
* `name`: the name of the parameter;
* `type`: the canonical type of the parameter.
- `outputs`: an array of objects similar to `inputs`, can be omitted if function doesn't return anything;
- `constant`: `true` if function is :ref:`specified to not modify blockchain state <constant-functions>`);
- `payable`: `true` if function accepts ether, defaults to `false`.
`type` can be omitted, defaulting to `"function"`.
Constructor and fallback function never have `name` or `outputs`. Fallback function doesn't have `inputs` either.
Sending non-zero ether to non-payable function will throw. Don't do it.
An event description is a JSON object with fairly similar fields:
- `type`: always `"event"`
- `name`: the name of the event;
- `inputs`: an array of objects, each of which contains:
* `name`: the name of the parameter;
* `type`: the canonical type of the parameter.
* `indexed`: `true` if the field is part of the log's topics, `false` if it one of the log's data segment.
- `anonymous`: `true` if the event was declared as `anonymous`.
For example,
::
contract Test {
function Test(){ b = 0x12345678901234567890123456789012; }
event Event(uint indexed a, bytes32 b)
event Event2(uint indexed a, bytes32 b)
function foo(uint a) { Event(a, b); }
bytes32 b;
}
would result in the JSON:
.. code:: json
[{
"type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event"
}, {
"type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event2"
}, {
"type":"event",
"inputs": [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"bytes32","indexed":false}],
"name":"Event2"
}, {
"type":"function",
"inputs": [{"name":"a","type":"uint256"}],
"name":"foo",
"outputs": []
}]

View File

@ -13,6 +13,8 @@ TODO: Write about how scoping rules of inline assembly are a bit different
and the complications that arise when for example using internal functions
of libraries. Furthermore, write about the symbols defined by the compiler.
.. _inline-assembly:
Inline Assembly
===============
@ -28,11 +30,8 @@ arising when writing manual assembly by the following features:
* 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))``
* loops: ``for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) }``
* switch statements: ``switch x case 0: { y := mul(x, 2) } default: { y := 0 }``
* function calls: ``function f(x) -> y { switch x case 0: { y := 1 } default: { y := mul(x, f(sub(x, 1))) } }``
.. note::
Of the above, loops, function calls and switch statements are not yet implemented.
* switch statements: ``switch x case 0 { y := mul(x, 2) } default { y := 0 }``
* function calls: ``function f(x) -> y { switch x case 0 { y := 1 } default { y := mul(x, f(sub(x, 1))) } }``
We now want to describe the inline assembly language in detail.
@ -182,6 +181,8 @@ In the grammar, opcodes are represented as pre-defined identifiers.
+-------------------------+------+-----------------------------------------------------------------+
| signextend(i, x) | | sign extend from (i*8+7)th bit counting from least significant |
+-------------------------+------+-----------------------------------------------------------------+
| keccak256(p, n) | | keccak(mem[p...(p+n))) |
+-------------------------+------+-----------------------------------------------------------------+
| sha3(p, n) | | keccak(mem[p...(p+n))) |
+-------------------------+------+-----------------------------------------------------------------+
| jump(label) | `-` | jump to label / code position |
@ -232,9 +233,17 @@ In the grammar, opcodes are represented as pre-defined identifiers.
+-------------------------+------+-----------------------------------------------------------------+
| extcodecopy(a, t, f, s) | `-` | like codecopy(t, f, s) but take code at address a |
+-------------------------+------+-----------------------------------------------------------------+
| returndatasize | | size of the last returndata |
+-------------------------+------+-----------------------------------------------------------------+
| returndatacopy(t, f, s) | `-` | copy s bytes from returndata at position f to mem at position t |
+-------------------------+------+-----------------------------------------------------------------+
| create(v, p, s) | | create new contract with code mem[p..(p+s)) and send v wei |
| | | and return the new address |
+-------------------------+------+-----------------------------------------------------------------+
| create2(v, n, p, s) | | create new contract with code mem[p..(p+s)) at address |
| | | keccak256(<address> . n . keccak256(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) |
@ -246,6 +255,9 @@ In the grammar, opcodes are represented as pre-defined identifiers.
| delegatecall(g, a, in, | | identical to `callcode` but also keep ``caller`` |
| insize, out, outsize) | | and ``callvalue`` |
+-------------------------+------+-----------------------------------------------------------------+
| staticcall(g, a, in, | | identical to `call(g, a, 0, in, insize, out, outsize)` but do |
| insize, out, outsize) | | not allow state modifications |
+-------------------------+------+-----------------------------------------------------------------+
| return(p, s) | `-` | end execution, return data mem[p..(p+s)) |
+-------------------------+------+-----------------------------------------------------------------+
| revert(p, s) | `-` | end execution, revert state changes, return data mem[p..(p+s)) |
@ -312,8 +324,10 @@ would be written as follows
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).
Functional style expressions cannot use instructional style internally, i.e.
``1 2 mstore(0x80, add)`` is not valid assembly, it has to be written as
``mstore(0x80, add(2, 1))``. For opcodes that do not take arguments, the
parentheses can be omitted.
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.
@ -431,11 +445,6 @@ As an example how this can be done in extreme cases, please see the following.
pop // We have to pop the manually pushed value here again.
}
.. 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
----------------------------------
@ -491,9 +500,6 @@ is performed by replacing the variable's value on the stack by the new value.
Switch
------
.. note::
Switch is not yet implemented.
You can use a switch statement as a very basic version of "if/else".
It takes the value of an expression and compares it to several constants.
The branch corresponding to the matching constant is taken. Contrary to the
@ -506,10 +512,10 @@ case called ``default``.
assembly {
let x := 0
switch calldataload(4)
case 0: {
case 0 {
x := calldataload(0x24)
}
default: {
default {
x := calldataload(0x44)
}
sstore(0, div(x, 2))
@ -521,13 +527,10 @@ case does require them.
Loops
-----
.. note::
Loops are not yet implemented.
Assembly supports a simple for-style loop. For-style loops have
a header containing an initializing part, a condition and a post-iteration
part. The condition has to be a functional-style expression, while
the other two can also be blocks. If the initializing part is a block that
the other two are blocks. If the initializing part
declares any variables, the scope of these variables is extended into the
body (including the condition and the post-iteration part).
@ -545,9 +548,6 @@ The following example computes the sum of an area in memory.
Functions
---------
.. note::
Functions are not yet implemented.
Assembly allows the definition of low-level functions. These take their
arguments (and a return PC) from the stack and also put the results onto the
stack. Calling a function looks the same way as executing a functional-style
@ -559,7 +559,7 @@ defined outside of that function. There is no explicit ``return``
statement.
If you call a function that returns multiple values, you have to assign
them to a tuple using ``(a, b) := f(x)`` or ``let (a, b) := f(x)``.
them to a tuple using ``a, b := f(x)`` or ``let a, b := f(x)``.
The following example implements the power function by square-and-multiply.
@ -568,12 +568,12 @@ The following example implements the power function by square-and-multiply.
assembly {
function power(base, exponent) -> result {
switch exponent
0: { result := 1 }
1: { result := base }
default: {
case 0 { result := 1 }
case 1 { result := base }
default {
result := power(mul(base, base), div(exponent, 2))
switch mod(exponent, 2)
1: { result := mul(base, result) }
case 1 { result := mul(base, result) }
}
}
}
@ -693,13 +693,13 @@ The following assembly will be generated::
mstore(0x40, 0x60) // store the "free memory pointer"
// function dispatcher
switch div(calldataload(0), exp(2, 226))
case 0xb3de648b: {
case 0xb3de648b {
let (r) = f(calldataload(4))
let ret := $allocate(0x20)
mstore(ret, r)
return(ret, 0x20)
}
default: { jump(invalidJumpLabel) }
default { revert(0, 0) }
// memory allocator
function $allocate(size) -> pos {
pos := mload(0x40)
@ -744,7 +744,7 @@ After the desugaring phase it looks as follows::
}
$caseDefault:
{
jump(invalidJumpLabel)
revert(0, 0)
jump($endswitch)
}
$endswitch:
@ -850,8 +850,8 @@ Grammar::
AssemblyAssignment = '=:' Identifier
LabelDefinition = Identifier ':'
AssemblySwitch = 'switch' FunctionalAssemblyExpression AssemblyCase*
( 'default' ':' AssemblyBlock )?
AssemblyCase = 'case' FunctionalAssemblyExpression ':' AssemblyBlock
( 'default' AssemblyBlock )?
AssemblyCase = 'case' FunctionalAssemblyExpression AssemblyBlock
AssemblyFunctionDefinition = 'function' Identifier '(' IdentifierList? ')'
( '->' '(' IdentifierList ')' )? AssemblyBlock
AssemblyFor = 'for' ( AssemblyBlock | FunctionalAssemblyExpression)

View File

@ -1,4 +1,11 @@
[
{
"name": "SkipEmptyStringLiteral",
"summary": "If \"\" is used in a function call, the following function arguments will not be correctly passed to the function.",
"description": "If the empty string literal \"\" is used as an argument in a function call, it is skipped by the encoder. This has the effect that the encoding of all arguments following this is shifted left by 32 bytes and thus the function call data is corrupted.",
"fixed": "0.4.12",
"severity": "low"
},
{
"name": "ConstantOptimizerSubtraction",
"summary": "In some situations, the optimizer replaces certain numbers in the code with routines that compute different numbers.",

View File

@ -1,6 +1,7 @@
{
"0.1.0": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStaleKnowledgeAboutSHA3",
@ -15,6 +16,7 @@
},
"0.1.1": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStaleKnowledgeAboutSHA3",
@ -29,6 +31,7 @@
},
"0.1.2": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStaleKnowledgeAboutSHA3",
@ -43,6 +46,7 @@
},
"0.1.3": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStaleKnowledgeAboutSHA3",
@ -57,6 +61,7 @@
},
"0.1.4": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStaleKnowledgeAboutSHA3",
@ -71,6 +76,7 @@
},
"0.1.5": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStaleKnowledgeAboutSHA3",
@ -85,6 +91,7 @@
},
"0.1.6": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@ -100,6 +107,7 @@
},
"0.1.7": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@ -115,6 +123,7 @@
},
"0.2.0": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@ -130,6 +139,7 @@
},
"0.2.1": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@ -145,6 +155,7 @@
},
"0.2.2": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@ -160,6 +171,7 @@
},
"0.3.0": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@ -174,6 +186,7 @@
},
"0.3.1": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@ -187,6 +200,7 @@
},
"0.3.2": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@ -200,6 +214,7 @@
},
"0.3.3": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@ -212,6 +227,7 @@
},
"0.3.4": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@ -224,6 +240,7 @@
},
"0.3.5": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@ -236,6 +253,7 @@
},
"0.3.6": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@ -246,6 +264,7 @@
},
"0.4.0": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@ -256,6 +275,7 @@
},
"0.4.1": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@ -266,16 +286,24 @@
},
"0.4.10": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction"
],
"released": "2017-03-15"
},
"0.4.11": {
"bugs": [],
"bugs": [
"SkipEmptyStringLiteral"
],
"released": "2017-05-03"
},
"0.4.12": {
"bugs": [],
"released": "2017-07-03"
},
"0.4.2": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage",
@ -285,6 +313,7 @@
},
"0.4.3": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"HighOrderByteCleanStorage"
@ -293,6 +322,7 @@
},
"0.4.4": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored"
],
@ -300,6 +330,7 @@
},
"0.4.5": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored",
"OptimizerStateKnowledgeNotResetForJumpdest"
@ -308,6 +339,7 @@
},
"0.4.6": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction",
"IdentityPrecompileReturnIgnored"
],
@ -315,18 +347,21 @@
},
"0.4.7": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction"
],
"released": "2016-12-15"
},
"0.4.8": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction"
],
"released": "2017-01-13"
},
"0.4.9": {
"bugs": [
"SkipEmptyStringLiteral",
"ConstantOptimizerSubtraction"
],
"released": "2017-01-31"

View File

@ -20,7 +20,7 @@ Contracts can be created "from outside" or from Solidity contracts.
When a contract is created, its constructor (a function with the same
name as the contract) is executed once.
A constructor is optional. Only one constructor is allowed and this means
A constructor is optional. Only one constructor is allowed, and this means
overloading is not supported.
From ``web3.js``, i.e. the JavaScript
@ -244,6 +244,7 @@ In the following example, ``D``, can call ``c.getData()`` to retrieve the value
}
.. index:: ! getter;function, ! function;getter
.. _getter_functions:
Getter Functions
================
@ -273,7 +274,7 @@ be done at declaration.
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
it is evaluated as a state variable. If it is accessed externally
(i.e. with ``this.``), it is evaluated as a function.
::
@ -321,8 +322,8 @@ is no good way to provide the key for the mapping.
Function Modifiers
******************
Modifiers can be used to easily change the behaviour of functions, for example
to automatically check a condition prior to executing the function. They are
Modifiers can be used to easily change the behaviour of functions. For example,
they can automatically check a condition prior to executing the function. Modifiers are
inheritable properties of contracts and may be overridden by derived contracts.
::
@ -405,8 +406,8 @@ inheritable properties of contracts and may be overridden by derived contracts.
}
}
Multiple modifiers can be applied to a function by specifying them in a
whitespace-separated list and will be evaluated in order.
Multiple modifiers are applied to a function by specifying them in a
whitespace-separated list and are evaluated in the order presented.
.. warning::
In an earlier version of Solidity, ``return`` statements in functions
@ -441,7 +442,7 @@ 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
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
@ -458,11 +459,13 @@ value types and strings.
}
.. _constant-functions:
******************
Constant Functions
******************
Functions can be declared constant. These functions promise not to modify the state.
Functions can be declared constant in which case they promise not to modify the state.
::
@ -491,7 +494,7 @@ Fallback Function
A contract can have exactly one unnamed function. This function cannot have
arguments and cannot return anything.
It is executed on a call to the contract if none of the other
functions matches the given function identifier (or if no data was supplied at
functions match the given function identifier (or if no data was supplied at
all).
Furthermore, this function is executed whenever the contract receives plain
@ -509,7 +512,8 @@ In particular, the following operations will consume more gas than the stipend p
Please ensure you test your fallback function thoroughly to ensure the execution cost is less than 2300 gas before deploying a contract.
.. warning::
Contracts that receive Ether but do not define a fallback function
Contracts that receive Ether directly (without a function call, i.e. using ``send`` or ``transfer``)
but do not define a fallback function
throw an exception, sending back the Ether (this was different
before Solidity v0.4.0). So if you want your contract to receive Ether,
you have to implement a fallback function.
@ -567,13 +571,12 @@ the contract and will be incorporated into the blockchain
and stay there as long as a block is accessible (forever as of
Frontier and Homestead, but this might change with Serenity). Log and
event data is not accessible from within contracts (not even from
the contract that created a log).
the contract that created them).
SPV proofs for logs are possible, so if an external entity supplies
a contract with such a proof, it can check that the log actually
exists inside the blockchain (but be aware of the fact that
ultimately, also the block headers have to be supplied because
the contract can only see the last 256 block hashes).
exists inside the blockchain. But be aware that block headers have to be supplied because
the contract can only see the last 256 block hashes.
Up to three parameters can
receive the attribute ``indexed`` which will cause the respective arguments
@ -590,7 +593,7 @@ not possible to filter for specific anonymous events by name.
All non-indexed arguments will be stored in the data part of the log.
.. note::
Indexed arguments will not be stored themselves, you can only
Indexed arguments will not be stored themselves. You can only
search for the values, but it is impossible to retrieve the
values themselves.
@ -605,7 +608,7 @@ All non-indexed arguments will be stored in the data part of the log.
uint _value
);
function deposit(bytes32 _id) {
function deposit(bytes32 _id) payable {
// Any call to this function (even deeply nested) can
// be detected from the JavaScript API by filtering
// for `Deposit` to be called.
@ -679,9 +682,9 @@ Solidity supports multiple inheritance by copying code including polymorphism.
All function calls are virtual, which means that the most derived function
is called, except when the contract name is explicitly given.
Even if a contract inherits from multiple other contracts, only a single
contract is created on the blockchain, the code from the base contracts
is always copied into the final contract.
When a contract inherits from multiple contracts, only a single
contract is created on the blockchain, and the code from all the base contracts
is copied into the created contract.
The general inheritance system is very similar to
`Python's <https://docs.python.org/3/tutorial/classes.html#inheritance>`_,
@ -818,7 +821,7 @@ derived override, but this function will bypass
}
If ``Base1`` calls a function of ``super``, it does not simply
call this function on one of its base contracts, it rather
call this function on one of its base contracts. Rather, it
calls this function on the next base contract in the final
inheritance graph, so it will call ``Base2.kill()`` (note that
the final inheritance sequence is -- starting with the most
@ -834,7 +837,7 @@ Arguments for Base Constructors
===============================
Derived contracts need to provide all arguments needed for
the base constructors. This can be done at two places::
the base constructors. This can be done in two ways::
pragma solidity ^0.4.0;
@ -849,7 +852,7 @@ the base constructors. This can be done at two places::
}
}
Either directly in the inheritance list (``is Base(7)``) or in
One way is directly in the inheritance list (``is Base(7)``). The other is in
the way a modifier would be invoked as part of the header of
the derived constructor (``Base(_y * _y)``). The first way to
do it is more convenient if the constructor argument is a
@ -865,7 +868,7 @@ Multiple Inheritance and Linearization
======================================
Languages that allow multiple inheritance have to deal with
several problems, one of them being the `Diamond Problem <https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem>`_.
several problems. One is the `Diamond Problem <https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem>`_.
Solidity follows the path of Python and uses "`C3 Linearization <https://en.wikipedia.org/wiki/C3_linearization>`_"
to force a specific order in the DAG of base classes. This
results in the desirable property of monotonicity but
@ -937,7 +940,7 @@ Interfaces are similar to abstract contracts, but they cannot have any functions
Some of these restrictions might be lifted in the future.
Interfaces are basically limited to what the Contract ABI can represent and the conversion between the ABI and
Interfaces are basically limited to what the Contract ABI can represent, and the conversion between the ABI and
an Interface should be possible without any information loss.
Interfaces are denoted by their own keyword:
@ -976,9 +979,9 @@ contracts (``L.f()`` if ``L`` is the name of the library). Furthermore,
if the library were a base contract. Of course, calls to internal functions
use the internal calling convention, which means that all internal types
can be passed and memory types will be passed by reference and not copied.
In order to realise this in the EVM, code of internal library functions
(and all functions called from therein) will be pulled into the calling
contract and a regular ``JUMP`` call will be used instead of a ``DELEGATECALL``.
To realize this in the EVM, code of internal library functions
and all functions called from therein will be pulled into the calling
contract, and a regular ``JUMP`` call will be used instead of a ``DELEGATECALL``.
.. index:: using for, set
@ -1041,8 +1044,8 @@ more advanced example to implement a set).
Of course, you do not have to follow this way to use
libraries - they can also be used without defining struct
data types, functions also work without any storage
reference parameters, can have multiple storage reference
data types. Functions also work without any storage
reference parameters, and they can have multiple storage reference
parameters and in any position.
The calls to ``Set.contains``, ``Set.insert`` and ``Set.remove``

View File

@ -12,7 +12,7 @@ In particular, we need help in the following areas:
* Improving the documentation
* Responding to questions from other users on `StackExchange
<http://ethereum.stackexchange.com/>`_ and the `Solidity Gitter
<https://ethereum.stackexchange.com>`_ and the `Solidity Gitter
<https://gitter.im/ethereum/solidity>`_
* Fixing and responding to `Solidity's GitHub issues
<https://github.com/ethereum/solidity/issues>`_, especially those tagged as
@ -74,3 +74,22 @@ To run a subset of tests, filters can be used:
``soltest -t TestSuite/TestName -- --ipcpath /tmp/testeth/geth.ipc``, where ``TestName`` can be a wildcard ``*``.
Alternatively, there is a testing script at ``scripts/test.sh`` which executes all tests.
Whiskers
========
*Whiskers* is a templating system similar to `Moustache <https://mustache.github.io>`_. It is used by the
compiler in various places to aid readability, and thus maintainability and verifiability, of the code.
The syntax comes with a substantial difference to Moustache: the template markers ``{{`` and ``}}`` are
replaced by ``<`` and ``>`` in order to aid parsing and avoid conflicts with :ref:`inline-assembly`
(The symbols ``<`` and ``>`` are invalid in inline assembly, while ``{`` and ``}`` are used to delimit blocks).
Another limitation is that lists are only resolved one depth and they will not recurse. This may change in the future.
A rough specification is the following:
Any occurrence of ``<name>`` is replaced by the string-value of the supplied variable ``name`` without any
escaping and without iterated replacements. An area can be delimited by ``<#name>...</name>``. It is replaced
by as many concatenations of its contents as there were sets of variables supplied to the template system,
each time replacing any ``<inner>`` items by their respective value. Top-level variales can also be used
inside such areas.

View File

@ -361,55 +361,72 @@ As a result, the following code is legal, despite being poorly written::
return bar;// returns 5
}
.. index:: ! exception, ! throw
.. index:: ! exception, ! throw, ! assert, ! require, ! revert
Exceptions
==========
Error handling: Assert, Require, Revert and Exceptions
======================================================
There are some cases where exceptions are thrown automatically (see below). You can use the ``throw`` instruction to throw an exception manually. The effect of an exception is that the currently executing call is stopped and reverted (i.e. all changes to the state and balances are undone) and the exception is also "bubbled up" through Solidity function calls (exceptions are ``send`` and the low-level functions ``call``, ``delegatecall`` and ``callcode``, those return ``false`` in case of an exception).
Solidity uses state-reverting exceptions to handle errors. Such an exception will undo all changes made to the
state in the current call (and all its sub-calls) and also flag an error to the caller.
The convenience functions ``assert`` and ``require`` can be used to check for conditions and throw an exception
if the condition is not met. The difference between the two is that ``assert`` should only be used for internal errors
and ``require`` should be used to check external conditions (invalid inputs or errors in external components).
The idea behind that is that analysis tools can check your contract and try to come up with situations and
series of function calls that will reach a failing assertion. If this is possible, this means there is a bug
in your contract you should fix.
There are two other ways to trigger execptions: The ``revert`` function can be used to flag an error and
revert the current call. In the future it might be possible to also include details about the error
in a call to ``revert``. The ``throw`` keyword can also be used as an alternative to ``revert()``.
When exceptions happen in a sub-call, they "bubble up" (i.e. exceptions are rethrown) automatically. Exceptions to this rule are ``send``
and the low-level functions ``call``, ``delegatecall`` and ``callcode`` -- those return ``false`` in case
of an exception instead of "bubbling up".
Catching exceptions is not yet possible.
In the following example, we show how ``throw`` can be used to easily revert an Ether transfer and also how to check the return value of ``send``::
In the following example, you can see how ``require`` can be used to easily check conditions on inputs
and how ``assert`` can be used for internal error checking::
pragma solidity ^0.4.0;
contract Sharer {
function sendHalf(address addr) payable returns (uint balance) {
if (!addr.send(msg.value / 2))
throw; // also reverts the transfer to Sharer
require(msg.value % 2 == 0); // Only allow even numbers
uint balanceBeforeTransfer = this.balance;
addr.transfer(msg.value / 2);
// Since transfer throws an exception on failure and
// cannot call back here, there should be no way for us to
// still have half of the money.
assert(this.balance == balanceBeforeTransfer - msg.value / 2);
return this.balance;
}
}
Currently, Solidity automatically generates a runtime exception in the following situations:
An ``assert``-style exception is generated in the following situations:
#. 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:
A ``require``-style exception is generated in the following situations:
#. Calling ``throw``.
#. Calling ``require`` with an argument that evaluates to ``false``.
#. 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 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 a ``.transfer()`` fails.
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
Internally, Solidity performs a revert operation (instruction ``0xfd``) for a ``require``-style exception and executes an invalid operation
(instruction ``0xfe``) to throw an ``assert``-style exception. In both cases, this causes
the EVM to revert all changes made to the state. The reason for reverting 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.
(or at least call) without effect. Note that ``assert``-style exceptions consume all gas available to the call, while
``revert``-style exceptions will not consume any gas starting from the Metropolis release.

View File

@ -7,7 +7,7 @@ ImportDirective = 'import' StringLiteral ('as' Identifier)? ';'
| 'import' ('*' | Identifier) ('as' Identifier)? 'from' StringLiteral ';'
| 'import' '{' Identifier ('as' Identifier)? ( ',' Identifier ('as' Identifier)? )* '}' 'from' StringLiteral ';'
ContractDefinition = ( 'contract' | 'library' ) Identifier
ContractDefinition = ( 'contract' | 'library' | 'interface' ) Identifier
( 'is' InheritanceSpecifier (',' InheritanceSpecifier )* )?
'{' ContractPart* '}'
@ -20,9 +20,12 @@ StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' )? Ident
UsingForDeclaration = 'using' Identifier 'for' ('*' | TypeName) ';'
StructDefinition = 'struct' Identifier '{'
( VariableDeclaration ';' (VariableDeclaration ';')* )? '}'
ModifierDefinition = 'modifier' Identifier ParameterList? Block
ModifierInvocation = Identifier ( '(' ExpressionList? ')' )?
FunctionDefinition = 'function' Identifier? ParameterList
( FunctionCall | Identifier | 'constant' | 'payable' | 'external' | 'public' | 'internal' | 'private' )*
( ModifierInvocation | 'constant' | 'payable' | 'external' | 'public' | 'internal' | 'private' )*
( 'returns' ParameterList )? ( ';' | Block )
EventDefinition = 'event' Identifier IndexedParameterList 'anonymous'? ';'
@ -62,7 +65,7 @@ WhileStatement = 'while' '(' Expression ')' Statement
PlaceholderStatement = '_'
SimpleStatement = VariableDefinition | ExpressionStatement
ForStatement = 'for' '(' (SimpleStatement)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement
InlineAssemblyStatement = 'assembly' InlineAssemblyBlock
InlineAssemblyStatement = 'assembly' StringLiteral? InlineAssemblyBlock
DoWhileStatement = 'do' Statement 'while' '(' Expression ')'
Continue = 'continue'
Break = 'break'
@ -72,8 +75,13 @@ VariableDefinition = ('var' IdentifierList | VariableDeclaration) ( '=' Expressi
IdentifierList = '(' ( Identifier? ',' )* Identifier? ')'
// Precedence by order (see github.com/ethereum/solidity/pull/732)
Expression =
( Expression ('++' | '--') | FunctionCall | IndexAccess | MemberAccess | '(' Expression ')' )
Expression
= Expression ('++' | '--')
| NewExpression
| IndexAccess
| MemberAccess
| FunctionCall
| '(' Expression ')'
| ('!' | '~' | 'delete' | '++' | '--' | '+' | '-') Expression
| Expression '**' Expression
| Expression ('*' | '/' | '%') Expression
@ -88,20 +96,20 @@ Expression =
| Expression '||' Expression
| Expression '?' Expression ':' Expression
| Expression ('=' | '|=' | '^=' | '&=' | '<<=' | '>>=' | '+=' | '-=' | '*=' | '/=' | '%=') Expression
| Expression? (',' Expression)
| PrimaryExpression
PrimaryExpression = Identifier
| BooleanLiteral
PrimaryExpression = BooleanLiteral
| NumberLiteral
| HexLiteral
| StringLiteral
| TupleExpression
| Identifier
| ElementaryTypeNameExpression
ExpressionList = Expression ( ',' Expression )*
NameValueList = Identifier ':' Expression ( ',' Identifier ':' Expression )*
FunctionCall = ( PrimaryExpression | NewExpression | TypeName ) ( ( '.' Identifier ) | ( '[' Expression ']' ) )* '(' FunctionCallArguments ')'
FunctionCall = Expression '(' FunctionCallArguments ')'
FunctionCallArguments = '{' NameValueList? '}'
| ExpressionList?
@ -120,6 +128,9 @@ Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]*
HexNumber = '0x' [0-9a-fA-F]+
DecimalNumber = [0-9]+
TupleExpression = '(' ( Expression ( ',' Expression )* )? ')'
| '[' ( Expression ( ',' Expression )* )? ']'
ElementaryTypeNameExpression = ElementaryTypeName
ElementaryTypeName = 'address' | 'bool' | 'string' | 'var'
@ -137,7 +148,8 @@ Ufixed = 'ufixed' | 'ufixed0x8' | 'ufixed0x16' | 'ufixed0x24' | 'ufixed0x32' | '
InlineAssemblyBlock = '{' AssemblyItem* '}'
AssemblyItem = Identifier | FunctionalAssemblyExpression | InlineAssemblyBlock | AssemblyLocalBinding | AssemblyAssignment | NumberLiteral | StringLiteral | HexLiteral
AssemblyItem = Identifier | FunctionalAssemblyExpression | InlineAssemblyBlock | AssemblyLocalBinding | AssemblyAssignment | AssemblyLabel | NumberLiteral | StringLiteral | HexLiteral
AssemblyLocalBinding = 'let' Identifier ':=' FunctionalAssemblyExpression
AssemblyAssignment = Identifier ':=' FunctionalAssemblyExpression | '=:' Identifier
AssemblyAssignment = ( Identifier ':=' FunctionalAssemblyExpression ) | ( '=:' Identifier )
AssemblyLabel = Identifier ':'
FunctionalAssemblyExpression = Identifier '(' AssemblyItem? ( ',' AssemblyItem )* ')'

View File

@ -1,6 +1,11 @@
Solidity
========
.. image:: logo.svg
:width: 120px
:alt: Solidity logo
:align: center
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 (EVM).
@ -54,6 +59,9 @@ Available Solidity Integrations
* `Atom Solidity Linter <https://atom.io/packages/linter-solidity>`_
Plugin for the Atom editor that provides Solidity linting.
* `Atom Solium Linter <https://atom.io/packages/linter-solium>`_
Configurable Solidty linter for Atom using Solium as a base.
* `Solium <https://github.com/duaraghav8/Solium/>`_
A commandline linter for Solidity which strictly follows the rules prescribed by the `Solidity Style Guide <http://solidity.readthedocs.io/en/latest/style-guide.html>`_.
@ -137,6 +145,7 @@ Contents
solidity-in-depth.rst
security-considerations.rst
using-the-compiler.rst
abi-spec.rst
style-guide.rst
common-patterns.rst
bugs.rst

View File

@ -119,6 +119,12 @@ Install it using ``brew``:
# Install 0.4.8
brew install https://raw.githubusercontent.com/ethereum/homebrew-ethereum/77cce03da9f289e5a3ffe579840d3c5dc0a62717/solidity.rb
Gentoo Linux also provides a solidity package that can be installed using ``emerge``:
.. code:: bash
demerge ev-lang/solidity
.. _building-from-source:
Building from Source
@ -261,7 +267,7 @@ 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 release example: ``0.4.8+commit.60cc1668.Emscripten.clang``.
A pre-release example: ``0.4.9-nightly.2017.1.17+commit.6ecb4aa3.Emscripten.clang``

View File

@ -33,9 +33,11 @@ Storage
The first line simply tells that the source code is written for
Solidity version 0.4.0 or anything newer that does not break functionality
(up to, but not including, version 0.5.0). This is to ensure that the
contract does not suddenly behave differently with a new compiler version.
contract does not suddenly behave differently with a new compiler version. The keyword ``pragma`` is called that way because, in general,
pragmas are instructions for the compiler about how to treat the
source code (e.g. `pragma once <https://en.wikipedia.org/wiki/Pragma_once>`_). .
A contract in the sense of Solidity is a collection of code (its functions) and
A contract in the sense of Solidity is a collection of code (its *functions*) and
data (its *state*) that resides at a specific address on the Ethereum
blockchain. The line ``uint storedData;`` declares a state variable called ``storedData`` of
type ``uint`` (unsigned integer of 256 bits). You can think of it as a single slot
@ -47,9 +49,9 @@ or retrieve the value of the variable.
To access a state variable, you do not need the prefix ``this.`` as is common in
other languages.
This contract does not yet do much apart from (due to the infrastructure
built by Ethereum) allowing anyone to store a single number that is accessible by
anyone in the world without (feasible) a way to prevent you from publishing
This contract does not do much yet (due to the infrastructure
built by Ethereum) apart from allowing anyone to store a single number that is accessible by
anyone in the world without a (feasible) way to prevent you from publishing
this number. Of course, anyone could just call ``set`` again with a different value
and overwrite your number, but the number will still be stored in the history
of the blockchain. Later, we will see how you can impose access restrictions
@ -124,7 +126,7 @@ get the idea - the compiler figures that out for you.
The next line, ``mapping (address => uint) public balances;`` also
creates a public state variable, but it is a more complex datatype.
The type maps addresses to unsigned integers.
Mappings can be seen as hashtables which are
Mappings can be seen as `hash tables <https://en.wikipedia.org/wiki/Hash_table>`_ which are
virtually initialized such that every possible key exists and is mapped to a
value whose byte-representation is all zeros. This analogy does not go
too far, though, as it is neither possible to obtain a list of all keys of
@ -193,7 +195,7 @@ Blockchain Basics
*****************
Blockchains as a concept are not too hard to understand for programmers. The reason is that
most of the complications (mining, hashing, elliptic-curve cryptography, peer-to-peer networks, ...)
most of the complications (mining, `hashing <https://en.wikipedia.org/wiki/Cryptographic_hash_function>`_, `elliptic-curve cryptography <https://en.wikipedia.org/wiki/Elliptic_curve_cryptography>`_, `peer-to-peer networks <https://en.wikipedia.org/wiki/Peer-to-peer>`_, etc.)
are just there to provide a certain set of features and promises. Once you accept these
features as given, you do not have to worry about the underlying technology - or do you have
to know how Amazon's AWS works internally in order to use it?
@ -402,7 +404,7 @@ such situations, so that exceptions "bubble up" the call stack.
As already said, the called contract (which can be the same as the caller)
will receive a freshly cleared instance of memory and has access to the
call payload - which will be provided in a separate area called the **calldata**.
After it finished execution, it can return data which will be stored at
After it has finished execution, it can return data which will be stored at
a location in the caller's memory preallocated by the caller.
Calls are **limited** to a depth of 1024, which means that for more complex
@ -423,8 +425,8 @@ address at runtime. Storage, current address and balance still
refer to the calling contract, only the code is taken from the called address.
This makes it possible to implement the "library" feature in Solidity:
Reusable library code that can be applied to a contract's storage in
order to e.g. implement a complex data structure.
Reusable library code that can be applied to a contract's storage, e.g. in
order to implement a complex data structure.
.. index:: log
@ -436,7 +438,7 @@ that maps all the way up to the block level. This feature called **logs**
is used by Solidity in order to implement **events**.
Contracts cannot access log data after it has been created, but they
can be efficiently accessed from outside the blockchain.
Since some part of the log data is stored in bloom filters, it is
Since some part of the log data is stored in `bloom filters <https://en.wikipedia.org/wiki/Bloom_filter>`_, it is
possible to search for this data in an efficient and cryptographically
secure way, so network peers that do not download the whole blockchain
("light clients") can still find these logs.

View File

@ -33,7 +33,7 @@ be sure that our code will compile the way we intended it to. We do not fix
the exact version of the compiler, so that bugfix releases are still possible.
It is possible to specify much more complex rules for the compiler version,
the expression follows those used by npm.
the expression follows those used by `npm <https://docs.npmjs.com/misc/semver>`_.
.. index:: source file, ! import
@ -185,7 +185,7 @@ Additionally, there is another type of comment called a natspec comment,
for which the documentation is not yet written. They are written with a
triple slash (``///``) or a double asterisk block(``/** ... */``) and
they should be used directly above function declarations or statements.
You can use Doxygen-style tags inside these comments to document
You can use `Doxygen <https://en.wikipedia.org/wiki/Doxygen>`_-style tags inside these comments to document
functions, annotate conditions for formal verification, and provide a
**confirmation text** which is shown to users when they attempt to invoke a
function.

27
docs/logo.svg Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="1300px" height="1300px"
viewBox="0 0 1300 1300" enable-background="new 0 0 1300 1300" xml:space="preserve">
<title>Vector 1</title>
<desc>Created with Sketch.</desc>
<g id="Page-1" sketch:type="MSPage">
<g id="solidity" transform="translate(402.000000, 118.000000)" sketch:type="MSLayerGroup">
<g id="Group" sketch:type="MSShapeGroup">
<path id="Shape" opacity="0.45" enable-background="new " d="M371.772,135.308L241.068,367.61H-20.158l130.614-232.302
H371.772"/>
<path id="Shape_1_" opacity="0.6" enable-background="new " d="M241.068,367.61h261.318L371.772,135.308H110.456
L241.068,367.61z"/>
<path id="Shape_2_" opacity="0.8" enable-background="new " d="M110.456,599.822L241.068,367.61L110.456,135.308
L-20.158,367.61L110.456,599.822z"/>
<path id="Shape_3_" opacity="0.45" enable-background="new " d="M111.721,948.275l130.704-232.303h261.318L373.038,948.275
H111.721"/>
<path id="Shape_4_" opacity="0.6" enable-background="new " d="M242.424,715.973H-18.893l130.613,232.303h261.317
L242.424,715.973z"/>
<path id="Shape_5_" opacity="0.8" enable-background="new " d="M373.038,483.761L242.424,715.973l130.614,232.303
l130.704-232.303L373.038,483.761z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -371,7 +371,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-getters>` for you for free.
* Make your state variables public - the compiler will create :ref:`getters <visibility-and-getters>` for you automatically.
* 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});``
@ -394,12 +394,14 @@ The following is the order of precedence for operators, listed in order of evalu
+============+=====================================+============================================+
| *1* | Postfix increment and decrement | ``++``, ``--`` |
+ +-------------------------------------+--------------------------------------------+
| | Function-like call | ``<func>(<args...>)`` |
| | New expression | ``new <typename>`` |
+ +-------------------------------------+--------------------------------------------+
| | Array subscripting | ``<array>[<index>]`` |
+ +-------------------------------------+--------------------------------------------+
| | Member access | ``<object>.<member>`` |
+ +-------------------------------------+--------------------------------------------+
| | Function-like call | ``<func>(<args...>)`` |
+ +-------------------------------------+--------------------------------------------+
| | Parentheses | ``(<statement>)`` |
+------------+-------------------------------------+--------------------------------------------+
| *2* | Prefix increment and decrement | ``++``, ``--`` |
@ -462,7 +464,7 @@ Global Variables
- ``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)
- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component)
- ``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()`

View File

@ -94,7 +94,7 @@ of votes.
// called incorrectly. But watch out, this
// will currently also consume all provided gas
// (this is planned to change in the future).
require((msg.sender == chairperson) && !voters[voter].voted);
require((msg.sender == chairperson) && !voters[voter].voted && (voters[voter].weight == 0));
voters[voter].weight = 1;
}
@ -165,7 +165,7 @@ of votes.
}
}
}
// Calls winningProposal() function to get the index
// of the winner contained in the proposals array and then
// returns the name of the winner
@ -273,7 +273,7 @@ activate themselves.
// If the bid is not higher, send the
// money back.
require(msg.value > highestBid);
if (highestBidder != 0) {
// Sending back the money by simply using
// highestBidder.send(highestBid) is a security risk
@ -296,7 +296,7 @@ activate themselves.
// before `send` returns.
pendingReturns[msg.sender] = 0;
if (!msg.sender.send(amount)) {
if (!msg.sender.send(amount)) {
// No need to call throw here, just reset the amount owing
pendingReturns[msg.sender] = amount;
return false;
@ -316,7 +316,7 @@ activate themselves.
// 3. interacting with other contracts
// If these phases are mixed up, the other contract could call
// back into the current contract and modify the state or cause
// effects (ether payout) to be perfromed multiple times.
// effects (ether payout) to be performed multiple times.
// If functions called internally include interaction with external
// contracts, they also have to be considered interaction with
// external contracts.
@ -566,9 +566,9 @@ Safe Remote Purchase
_;
}
event aborted();
event purchaseConfirmed();
event itemReceived();
event Aborted();
event PurchaseConfirmed();
event ItemReceived();
/// Abort the purchase and reclaim the ether.
/// Can only be called by the seller before
@ -577,7 +577,7 @@ Safe Remote Purchase
onlySeller
inState(State.Created)
{
aborted();
Aborted();
state = State.Inactive;
seller.transfer(this.balance);
}
@ -591,7 +591,7 @@ Safe Remote Purchase
condition(msg.value == (2 * value))
payable
{
purchaseConfirmed();
PurchaseConfirmed();
buyer = msg.sender;
state = State.Locked;
}
@ -602,7 +602,7 @@ Safe Remote Purchase
onlyBuyer
inState(State.Locked)
{
itemReceived();
ItemReceived();
// It is important to change the state first because
// otherwise, the contracts called using `send` below
// can call in again here.
@ -612,7 +612,7 @@ Safe Remote Purchase
// block the refund - the withdraw pattern should be used.
buyer.transfer(value);
seller.transfer(this.balance));
seller.transfer(this.balance);
}
}

View File

@ -54,13 +54,13 @@ Operators:
* Bit operators: ``&``, ``|``, ``^`` (bitwise exclusive or), ``~`` (bitwise negation)
* Arithmetic operators: ``+``, ``-``, unary ``-``, unary ``+``, ``*``, ``/``, ``%`` (remainder), ``**`` (exponentiation), ``<<`` (left shift), ``>>`` (right shift)
Division always truncates (it just maps to the DIV opcode of the EVM), but it does not truncate if both
Division always truncates (it is just compiled to the DIV opcode of the EVM), but it does not truncate if both
operators are :ref:`literals<rational_literals>` (or literal expressions).
Division by zero and modulus with zero throws a runtime exception.
The result of a shift operation is the type of the left operand. The
expression ``x << y`` is equivalent to ``x * 2**y`` and ``x >> y`` is
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.
@ -77,7 +77,7 @@ sign extends. Shifting by a negative amount throws a runtime exception.
Address
-------
``address``: Holds a 20 byte value (size of an Ethereum address). Address types also have members and serve as base for all contracts.
``address``: Holds a 20 byte value (size of an Ethereum address). Address types also have members and serve as a base for all contracts.
Operators:
@ -125,7 +125,7 @@ the function ``call`` is provided which takes an arbitrary number of arguments o
``call`` returns a boolean indicating whether the invoked function terminated (``true``) or caused an EVM exception (``false``). It is not possible to access the actual data returned (for this we would need to know the encoding and size in advance).
In a similar way, the function ``delegatecall`` can be used: The difference is that only the code of the given address is used, all other aspects (storage, balance, ...) are taken from the current contract. The purpose of ``delegatecall`` is to use library code which is stored in another contract. The user has to ensure that the layout of storage in both contracts is suitable for delegatecall to be used. Prior to homestead, only a limited variant called ``callcode`` was available that did not provide access to the original ``msg.sender`` and ``msg.value`` values.
In a similar way, the function ``delegatecall`` can be used: the difference is that only the code of the given address is used, all other aspects (storage, balance, ...) are taken from the current contract. The purpose of ``delegatecall`` is to use library code which is stored in another contract. The user has to ensure that the layout of storage in both contracts is suitable for delegatecall to be used. Prior to homestead, only a limited variant called ``callcode`` was available that did not provide access to the original ``msg.sender`` and ``msg.value`` values.
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.
@ -387,10 +387,10 @@ Example that shows how to use internal function types::
}
function reduce(
uint[] memory self,
function (uint x, uint y) returns (uint) f
function (uint, uint) returns (uint) f
)
internal
returns (uint r)
returns (uint)
{
r = self[0];
for (uint i = 1; i < self.length; i++) {
@ -472,19 +472,19 @@ context, there is always a default, but it can be overridden by appending
either ``storage`` or ``memory`` to the type. The default for function parameters (including return parameters) is ``memory``, the default for local variables is ``storage`` and the location is forced
to ``storage`` for state variables (obviously).
There is also a third data location, "calldata", which is a non-modifyable
There is also a third data location, "calldata", which is a non-modifiable,
non-persistent area where function arguments are stored. Function parameters
(not return parameters) of external functions are forced to "calldata" and
it behaves mostly like memory.
behave mostly like memory.
Data locations are important because they change how assignments behave:
Assignments between storage and memory and also to a state variable (even from other state variables)
assignments between storage and memory and also to a state variable (even from other state variables)
always create an independent copy.
Assignments to local storage variables only assign a reference though, and
this reference always points to the state variable even if the latter is changed
in the meantime.
On the other hand, assignments from a memory stored reference type to another
memory-stored reference type does not create a copy.
memory-stored reference type do not create a copy.
::
@ -655,7 +655,8 @@ Members
contract ArrayContract {
uint[2**20] m_aLotOfIntegers;
// Note that the following is not a pair of arrays but an array of pairs.
// Note that the following is not a pair of dynamic arrays but a
// dynamic array of pairs (i.e. of fixed size arrays of length two).
bool[2][] m_pairsOfFlags;
// newPairs is stored in memory - the default for function arguments
@ -795,7 +796,7 @@ Mapping types are declared as ``mapping(_KeyType => _ValueType)``.
Here ``_KeyType`` can be almost any type except for a mapping, a dynamically sized array, a contract, an enum and a struct.
``_ValueType`` can actually be any type, including mappings.
Mappings can be seen as hashtables which are virtually initialized such that
Mappings can be seen as `hash tables <https://en.wikipedia.org/wiki/Hash_table>`_ which are virtually initialized such that
every possible key exists and is mapped to a value whose byte-representation is
all zeros: a type's :ref:`default value <default-value>`. The similarity ends here, though: The key data is not actually stored
in a mapping, only its ``keccak256`` hash used to look up the value.

View File

@ -55,7 +55,7 @@ Block and Transaction Properties
- ``block.difficulty`` (``uint``): current block difficulty
- ``block.gaslimit`` (``uint``): current block gaslimit
- ``block.number`` (``uint``): current block number
- ``block.timestamp`` (``uint``): current block timestamp
- ``block.timestamp`` (``uint``): current block timestamp as seconds since unix epoch
- ``msg.data`` (``bytes``): complete calldata
- ``msg.gas`` (``uint``): remaining gas
- ``msg.sender`` (``address``): sender of the message (current call)
@ -79,13 +79,23 @@ Block and Transaction Properties
You can only access the hashes of the most recent 256 blocks, all other
values will be zero.
.. index:: assert, revert, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send
.. index:: assert, revert, require
Error Handling
--------------
``assert(bool condition)``:
throws if the condition is not met - to be used for internal errors.
``require(bool condition)``:
throws if the condition is not met - to be used for errors in inputs or external components.
``revert()``:
abort execution and revert state changes
.. index:: keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography,
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)``:
@ -101,8 +111,6 @@ Mathematical and Cryptographic Functions
``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
(`example usage <https://ethereum.stackexchange.com/q/1777/222>`_)
``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::
@ -122,6 +130,7 @@ This means that, for example, ``keccak256(0) == keccak256(uint8(0))`` and
It might be that you run into Out-of-Gas for ``sha256``, ``ripemd160`` or ``ecrecover`` on a *private blockchain*. The reason for this is that those are implemented as so-called precompiled contracts and these contracts only really exist after they received the first message (although their contract code is hardcoded). Messages to non-existing contracts are more expensive and thus the execution runs into an Out-of-Gas error. A workaround for this problem is to first send e.g. 1 Wei to each of the contracts before you use them in your actual contracts. This is not an issue on the official or test net.
.. index:: balance, send, transfer, call, callcode, delegatecall
.. _address_related:
Address Related

View File

@ -119,7 +119,7 @@ Input Description
//
// The available output types are as follows:
// abi - ABI
// ast - AST of all source files (not supported atm)
// ast - AST of all source files
// legacyAST - legacy AST of all source files
// devdoc - Developer documentation (natspec)
// userdoc - User documentation (natspec)

View File

@ -26,7 +26,7 @@ class SolidityLexer(RegexLexer):
(r'/\*.*?\*/', Comment.Multiline)
],
'natspec': [
(r'@author|@dev|@notice|@return|@param|@why3|@title', Keyword),
(r'@author|@dev|@notice|@return|@param|@title', Keyword),
(r'.[^@*\n]*?', Comment.Special)
],
'docstringsingle': [

View File

@ -25,7 +25,6 @@
#pragma once
#include "Exceptions.h"
#include "debugbreak.h"
namespace dev
{
@ -38,37 +37,6 @@ namespace dev
#define ETH_FUNC __func__
#endif
#define asserts(A) ::dev::assertAux(A, #A, __LINE__, __FILE__, ETH_FUNC)
#define assertsEqual(A, B) ::dev::assertEqualAux(A, B, #A, #B, __LINE__, __FILE__, ETH_FUNC)
inline bool assertAux(bool _a, char const* _aStr, unsigned _line, char const* _file, char const* _func)
{
bool ret = _a;
if (!ret)
{
std::cerr << "Assertion failed:" << _aStr << " [func=" << _func << ", line=" << _line << ", file=" << _file << "]" << std::endl;
#if ETH_DEBUG
debug_break();
#endif
}
return !ret;
}
template<class A, class B>
inline bool assertEqualAux(A const& _a, B const& _b, char const* _aStr, char const* _bStr, unsigned _line, char const* _file, char const* _func)
{
bool ret = _a == _b;
if (!ret)
{
std::cerr << "Assertion failed: " << _aStr << " == " << _bStr << " [func=" << _func << ", line=" << _line << ", file=" << _file << "]" << std::endl;
std::cerr << " Fail equality: " << _a << "==" << _b << std::endl;
#if ETH_DEBUG
debug_break();
#endif
}
return !ret;
}
/// Assertion that throws an exception containing the given description if it is not met.
/// Use it as assertThrow(1 == 1, ExceptionType, "Mathematics is wrong.");
/// Do NOT supply an exception object as the second parameter.
@ -86,6 +54,4 @@ inline bool assertEqualAux(A const& _a, B const& _b, char const* _aStr, char con
} \
while (false)
using errinfo_comment = boost::error_info<struct tag_comment, std::string>;
}

View File

@ -76,8 +76,6 @@ using byte = uint8_t;
#define DEV_QUOTED_HELPER(s) #s
#define DEV_QUOTED(s) DEV_QUOTED_HELPER(s)
#define DEV_IGNORE_EXCEPTIONS(X) try { X; } catch (...) {}
namespace dev
{

View File

@ -30,11 +30,11 @@
#include <termios.h>
#endif
#include <boost/filesystem.hpp>
#include "Exceptions.h"
#include "Assertions.h"
using namespace std;
using namespace dev;
template <typename _T>
inline _T contentsGeneric(std::string const& _file)
{
@ -78,13 +78,24 @@ void dev::writeFile(std::string const& _file, bytesConstRef _data, bool _writeDe
if (!fs::exists(p.parent_path()))
{
fs::create_directories(p.parent_path());
DEV_IGNORE_EXCEPTIONS(fs::permissions(p.parent_path(), fs::owner_all));
try
{
fs::permissions(p.parent_path(), fs::owner_all);
}
catch (...)
{
}
}
ofstream s(_file, ios::trunc | ios::binary);
s.write(reinterpret_cast<char const*>(_data.data()), _data.size());
if (!s)
BOOST_THROW_EXCEPTION(FileError() << errinfo_comment("Could not write to file: " + _file));
DEV_IGNORE_EXCEPTIONS(fs::permissions(_file, fs::owner_read|fs::owner_write));
assertThrow(s, FileError, "Could not write to file: " + _file);
try
{
fs::permissions(_file, fs::owner_read|fs::owner_write);
}
catch (...)
{
}
}
}

View File

@ -56,9 +56,5 @@ DEV_SIMPLE_EXCEPTION(FileError);
// error information to be added to exceptions
using errinfo_invalidSymbol = boost::error_info<struct tag_invalidSymbol, char>;
using errinfo_comment = boost::error_info<struct tag_comment, std::string>;
using errinfo_required = boost::error_info<struct tag_required, bigint>;
using errinfo_got = boost::error_info<struct tag_got, bigint>;
using errinfo_required_h256 = boost::error_info<struct tag_required_h256, h256>;
using errinfo_got_h256 = boost::error_info<struct tag_get_h256, h256>;
}

View File

@ -29,28 +29,28 @@
namespace dev
{
// SHA-3 convenience routines.
// Keccak-256 convenience routines.
/// Calculate SHA3-256 hash of the given input and load it into the given output.
/// Calculate Keccak-256 hash of the given input and load it into the given output.
/// @returns false if o_output.size() != 32.
bool keccak256(bytesConstRef _input, bytesRef o_output);
/// Calculate SHA3-256 hash of the given input, returning as a 256-bit hash.
/// Calculate Keccak-256 hash of the given input, returning as a 256-bit hash.
inline h256 keccak256(bytesConstRef _input) { h256 ret; keccak256(_input, ret.ref()); return ret; }
/// Calculate SHA3-256 hash of the given input, returning as a 256-bit hash.
/// Calculate Keccak-256 hash of the given input, returning as a 256-bit hash.
inline h256 keccak256(bytes const& _input) { return keccak256(bytesConstRef(&_input)); }
/// Calculate SHA3-256 hash of the given input (presented as a binary-filled string), returning as a 256-bit hash.
/// Calculate Keccak-256 hash of the given input (presented as a binary-filled string), returning as a 256-bit hash.
inline h256 keccak256(std::string const& _input) { return keccak256(bytesConstRef(_input)); }
/// Calculate SHA3-256 hash of the given input (presented as a FixedHash), returns a 256-bit hash.
/// Calculate Keccak-256 hash of the given input (presented as a FixedHash), returns a 256-bit hash.
template<unsigned N> inline h256 keccak256(FixedHash<N> const& _input) { return keccak256(_input.ref()); }
/// Calculate SHA3-256 hash of the given input, possibly interpreting it as nibbles, and return the hash as a string filled with binary data.
/// Calculate Keccak-256 hash of the given input, possibly interpreting it as nibbles, and return the hash as a string filled with binary data.
inline std::string keccak256(std::string const& _input, bool _isNibbles) { return asString((_isNibbles ? keccak256(fromHex(_input)) : keccak256(bytesConstRef(&_input))).asBytes()); }
/// Calculate SHA3-256 MAC
/// Calculate Keccak-256 MAC
inline void keccak256mac(bytesConstRef _secret, bytesConstRef _plain, bytesRef _output) { keccak256(_secret.toBytes() + _plain.toBytes()).ref().populate(_output); }
}

View File

@ -27,25 +27,74 @@
namespace dev
{
bool validateUTF8(std::string const& _input, size_t& _invalidPosition)
namespace
{
/// Validate byte sequence against Unicode chapter 3 Table 3-7.
bool isWellFormed(unsigned char byte1, unsigned char byte2)
{
if (byte1 == 0xc0 || byte1 == 0xc1)
return false;
else if (byte1 >= 0xc2 && byte1 <= 0xdf)
return true;
else if (byte1 == 0xe0)
{
if (byte2 < 0xa0)
return false;
else
return true;
}
else if (byte1 >= 0xe1 && byte1 <= 0xec)
return true;
else if (byte1 == 0xed)
{
if (byte2 > 0x9f)
return false;
else
return true;
}
else if (byte1 == 0xee || byte1 == 0xef)
return true;
else if (byte1 == 0xf0)
{
if (byte2 < 0x90)
return false;
else
return true;
}
else if (byte1 >= 0xf1 && byte1 <= 0xf3)
return true;
else if (byte1 == 0xf4)
{
if (byte2 > 0x8f)
return false;
else
return true;
}
/// 0xf5 .. 0xf7 is disallowed
/// Technically anything below 0xc0 or above 0xf7 is
/// not possible to encode using Table 3-6 anyway.
return false;
}
bool validateUTF8(const unsigned char *_input, size_t _length, size_t& _invalidPosition)
{
const size_t length = _input.length();
bool valid = true;
size_t i = 0;
for (; i < length; i++)
for (; i < _length; i++)
{
if ((unsigned char)_input[i] < 0x80)
// Check for Unicode Chapter 3 Table 3-6 conformity.
if (_input[i] < 0x80)
continue;
size_t count = 0;
switch(_input[i] & 0xe0) {
case 0xc0: count = 1; break;
case 0xe0: count = 2; break;
case 0xf0: count = 3; break;
default: break;
}
if (_input[i] >= 0xc0 && _input[i] <= 0xdf)
count = 1;
else if (_input[i] >= 0xe0 && _input[i] <= 0xef)
count = 2;
else if (_input[i] >= 0xf0 && _input[i] <= 0xf7)
count = 3;
if (count == 0)
{
@ -53,7 +102,7 @@ bool validateUTF8(std::string const& _input, size_t& _invalidPosition)
break;
}
if ((i + count) >= length)
if ((i + count) >= _length)
{
valid = false;
break;
@ -67,6 +116,13 @@ bool validateUTF8(std::string const& _input, size_t& _invalidPosition)
valid = false;
break;
}
// Check for Unicode Chapter 3 Table 3-7 conformity.
if ((j == 0) && !isWellFormed(_input[i - 1], _input[i]))
{
valid = false;
break;
}
}
}
@ -77,5 +133,11 @@ bool validateUTF8(std::string const& _input, size_t& _invalidPosition)
return false;
}
}
bool validateUTF8(std::string const& _input, size_t& _invalidPosition)
{
return validateUTF8(reinterpret_cast<unsigned char const*>(_input.c_str()), _input.length(), _invalidPosition);
}
}

127
libdevcore/Whiskers.cpp Normal file
View File

@ -0,0 +1,127 @@
/*
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 Whiskers.cpp
* @author Chris <chis@ethereum.org>
* @date 2017
*
* Moustache-like templates.
*/
#include <libdevcore/Whiskers.h>
#include <libdevcore/Assertions.h>
#include <boost/regex.hpp>
using namespace std;
using namespace dev;
Whiskers::Whiskers(string const& _template):
m_template(_template)
{
}
Whiskers& Whiskers::operator ()(string const& _parameter, string const& _value)
{
assertThrow(
m_parameters.count(_parameter) == 0,
WhiskersError,
_parameter + " already set."
);
assertThrow(
m_listParameters.count(_parameter) == 0,
WhiskersError,
_parameter + " already set as list parameter."
);
m_parameters[_parameter] = _value;
return *this;
}
Whiskers& Whiskers::operator ()(
string const& _listParameter,
vector<map<string, string>> const& _values
)
{
assertThrow(
m_listParameters.count(_listParameter) == 0,
WhiskersError,
_listParameter + " already set."
);
assertThrow(
m_parameters.count(_listParameter) == 0,
WhiskersError,
_listParameter + " already set as value parameter."
);
m_listParameters[_listParameter] = _values;
return *this;
}
string Whiskers::render() const
{
return replace(m_template, m_parameters, m_listParameters);
}
string Whiskers::replace(
string const& _template,
StringMap const& _parameters,
map<string, vector<StringMap>> const& _listParameters
)
{
using namespace boost;
static regex listOrTag("<([^#/>]+)>|<#([^>]+)>(.*?)</\\2>");
return regex_replace(_template, listOrTag, [&](match_results<string::const_iterator> _match) -> string
{
string tagName(_match[1]);
if (!tagName.empty())
{
assertThrow(_parameters.count(tagName), WhiskersError, "Tag " + tagName + " not found.");
return _parameters.at(tagName);
}
else
{
string listName(_match[2]);
string templ(_match[3]);
assertThrow(!listName.empty(), WhiskersError, "");
assertThrow(
_listParameters.count(listName),
WhiskersError, "List parameter " + listName + " not set."
);
string replacement;
for (auto const& parameters: _listParameters.at(listName))
replacement += replace(templ, joinMaps(_parameters, parameters));
return replacement;
}
});
}
Whiskers::StringMap Whiskers::joinMaps(
Whiskers::StringMap const& _a,
Whiskers::StringMap const& _b
)
{
Whiskers::StringMap ret = _a;
for (auto const& x: _b)
assertThrow(
ret.insert(x).second,
WhiskersError,
"Parameter collision"
);
return ret;
}

87
libdevcore/Whiskers.h Normal file
View File

@ -0,0 +1,87 @@
/*
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 Whiskers.h
* @author Chris <chis@ethereum.org>
* @date 2017
*
* Moustache-like templates.
*/
#pragma once
#include <libdevcore/Exceptions.h>
#include <string>
#include <map>
#include <vector>
namespace dev
{
DEV_SIMPLE_EXCEPTION(WhiskersError);
///
/// Moustache-like templates.
///
/// Usage:
/// std::vector<std::map<std::string, std::string>> listValues(2);
/// listValues[0]["k"] = "key1";
/// listValues[0]["v"] = "value1";
/// listValues[1]["k"] = "key2";
/// listValues[1]["v"] = "value2";
/// auto s = Whiskers("<p1>\n<#list><k> -> <v>\n</list>")
/// ("p1", "HEAD")
/// ("list", listValues)
/// .render();
///
/// results in s == "HEAD\nkey1 -> value1\nkey2 -> value2\n"
///
/// Note that lists cannot themselves contain lists - this would be a future feature.
class Whiskers
{
public:
using StringMap = std::map<std::string, std::string>;
using StringListMap = std::map<std::string, std::vector<StringMap>>;
explicit Whiskers(std::string const& _template);
/// Sets a single parameter, <paramName>.
Whiskers& operator()(std::string const& _parameter, std::string const& _value);
/// Sets a list parameter, <#listName> </listName>.
Whiskers& operator()(
std::string const& _listParameter,
std::vector<StringMap> const& _values
);
std::string render() const;
private:
static std::string replace(
std::string const& _template,
StringMap const& _parameters,
StringListMap const& _listParameters = StringListMap()
);
/// Joins the two maps throwing an exception if two keys are equal.
static StringMap joinMaps(StringMap const& _a, StringMap const& _b);
std::string m_template;
StringMap m_parameters;
StringListMap m_listParameters;
};
}

View File

@ -1,128 +0,0 @@
/* Copyright (c) 2013, Scott Tsai
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef DEBUG_BREAK_H
#define DEBUG_BREAK_H
#if defined(_MSC_VER) || defined(__MINGW32__)
#define debug_break __debugbreak
#else
#include <signal.h>
#include <unistd.h>
#include <sys/syscall.h>
#ifdef __cplusplus
extern "C" {
#endif
enum {
/* gcc optimizers consider code after __builtin_trap() dead.
* Making __builtin_trap() unsuitable for breaking into the debugger */
DEBUG_BREAK_PREFER_BUILTIN_TRAP_TO_SIGTRAP = 0,
};
#if defined(__i386__) || defined(__x86_64__)
enum { HAVE_TRAP_INSTRUCTION = 1, };
__attribute__((gnu_inline, always_inline))
static void __inline__ trap_instruction(void)
{
__asm__ volatile("int $0x03");
}
#elif defined(__thumb__)
enum { HAVE_TRAP_INSTRUCTION = 1, };
/* FIXME: handle __THUMB_INTERWORK__ */
__attribute__((gnu_inline, always_inline))
static void __inline__ trap_instruction(void)
{
/* See 'arm-linux-tdep.c' in GDB source.
* Both instruction sequences below works. */
#if 1
/* 'eabi_linux_thumb_le_breakpoint' */
__asm__ volatile(".inst 0xde01");
#else
/* 'eabi_linux_thumb2_le_breakpoint' */
__asm__ volatile(".inst.w 0xf7f0a000");
#endif
/* Known problem:
* After a breakpoint hit, can't stepi, step, or continue in GDB.
* 'step' stuck on the same instruction.
*
* Workaround: a new GDB command,
* 'debugbreak-step' is defined in debugbreak-gdb.py
* that does:
* (gdb) set $instruction_len = 2
* (gdb) tbreak *($pc + $instruction_len)
* (gdb) jump *($pc + $instruction_len)
*/
}
#elif defined(__arm__) && !defined(__thumb__)
enum { HAVE_TRAP_INSTRUCTION = 1, };
__attribute__((gnu_inline, always_inline))
static void __inline__ trap_instruction(void)
{
/* See 'arm-linux-tdep.c' in GDB source,
* 'eabi_linux_arm_le_breakpoint' */
__asm__ volatile(".inst 0xe7f001f0");
/* Has same known problem and workaround
* as Thumb mode */
}
#elif defined(ETH_EMSCRIPTEN)
enum { HAVE_TRAP_INSTRUCTION = 1, };
__attribute__((gnu_inline, always_inline))
static void __inline__ trap_instruction(void)
{
asm("debugger");
}
#else
enum { HAVE_TRAP_INSTRUCTION = 0, };
#endif
__attribute__((gnu_inline, always_inline))
static void __inline__ debug_break(void)
{
if (HAVE_TRAP_INSTRUCTION) {
trap_instruction();
} else if (DEBUG_BREAK_PREFER_BUILTIN_TRAP_TO_SIGTRAP) {
/* raises SIGILL on Linux x86{,-64}, to continue in gdb:
* (gdb) handle SIGILL stop nopass
* */
__builtin_trap();
} else {
raise(SIGTRAP);
}
}
#ifdef __cplusplus
}
#endif
#endif
#endif

View File

@ -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 && i != errorTag()))
if (i.type() == Tag || i.type() == PushTag)
i.setData(i.data() + m_usedTags);
else if (i.type() == PushSub || i.type() == PushSubSize)
i.setData(i.data() + m_subs.size());
@ -55,28 +55,15 @@ void Assembly::append(Assembly const& _a)
m_subs += _a.m_subs;
for (auto const& lib: _a.m_libraries)
m_libraries.insert(lib);
assert(!_a.m_baseDeposit);
assert(!_a.m_totalDeposit);
}
void Assembly::append(Assembly const& _a, int _deposit)
{
if (_deposit > _a.m_deposit)
BOOST_THROW_EXCEPTION(InvalidDeposit());
else
{
append(_a);
while (_deposit++ < _a.m_deposit)
append(Instruction::POP);
}
}
assertThrow(_deposit <= _a.m_deposit, InvalidDeposit, "");
string Assembly::out() const
{
stringstream ret;
stream(ret);
return ret.str();
append(_a);
while (_deposit++ < _a.m_deposit)
append(Instruction::POP);
}
unsigned Assembly::bytesRequired(unsigned subTagSize) const
@ -216,6 +203,9 @@ ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap con
}
}
if (m_auxiliaryData.size() > 0)
_out << endl << _prefix << "auxdata: 0x" << toHex(m_auxiliaryData) << endl;
return _out;
}
@ -315,8 +305,13 @@ Json::Value Assembly::streamAsmJson(ostream& _out, StringMap const& _sourceCodes
data[hexStr.str()] = m_subs[i]->stream(_out, "", _sourceCodes, true);
}
root[".data"] = data;
_out << root;
}
if (m_auxiliaryData.size() > 0)
root[".auxdata"] = toHex(m_auxiliaryData);
_out << root;
return root;
}
@ -333,6 +328,7 @@ Json::Value Assembly::stream(ostream& _out, string const& _prefix, StringMap con
AssemblyItem const& Assembly::append(AssemblyItem const& _i)
{
assertThrow(m_deposit >= 0, AssemblyException, "");
m_deposit += _i.deposit();
m_items.push_back(_i);
if (m_items.back().location().isEmpty() && !m_currentSourceLocation.isEmpty())

View File

@ -69,7 +69,13 @@ public:
AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(solidity::Instruction::JUMPI); return ret; }
AssemblyItem appendJump(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(solidity::Instruction::JUMP); return ret; }
AssemblyItem appendJumpI(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(solidity::Instruction::JUMPI); return ret; }
AssemblyItem errorTag() { return AssemblyItem(PushTag, 0); }
/// Adds a subroutine to the code (in the data section) and pushes its size (via a tag)
/// on the stack. @returns the pushsub assembly item.
AssemblyItem appendSubroutine(AssemblyPointer const& _assembly) { auto sub = newSub(_assembly); append(newPushSubSize(size_t(sub.data()))); return sub; }
void pushSubroutineSize(size_t _subRoutine) { append(newPushSubSize(_subRoutine)); }
/// Pushes the offset of the subroutine.
void pushSubroutineOffset(size_t _subRoutine) { append(AssemblyItem(PushSub, _subRoutine)); }
/// Appends @a _data literally to the very end of the bytecode.
void appendAuxiliaryDataToEnd(bytes const& _data) { m_auxiliaryData += _data; }
@ -79,19 +85,10 @@ public:
AssemblyItem const& back() const { return m_items.back(); }
std::string backString() const { return m_items.size() && m_items.back().type() == PushString ? m_strings.at((h256)m_items.back().data()) : std::string(); }
void onePath() { if (asserts(!m_totalDeposit && !m_baseDeposit)) BOOST_THROW_EXCEPTION(InvalidDeposit()); m_baseDeposit = m_deposit; m_totalDeposit = INT_MAX; }
void otherPath() { donePath(); m_totalDeposit = m_deposit; m_deposit = m_baseDeposit; }
void donePaths() { donePath(); m_totalDeposit = m_baseDeposit = 0; }
void ignored() { m_baseDeposit = m_deposit; }
void endIgnored() { m_deposit = m_baseDeposit; m_baseDeposit = 0; }
void popTo(int _deposit) { while (m_deposit > _deposit) append(solidity::Instruction::POP); }
void injectStart(AssemblyItem const& _i);
std::string out() const;
int deposit() const { return m_deposit; }
void adjustDeposit(int _adjustment) { m_deposit += _adjustment; if (asserts(m_deposit >= 0)) BOOST_THROW_EXCEPTION(InvalidDeposit()); }
void setDeposit(int _deposit) { m_deposit = _deposit; if (asserts(m_deposit >= 0)) BOOST_THROW_EXCEPTION(InvalidDeposit()); }
void adjustDeposit(int _adjustment) { m_deposit += _adjustment; assertThrow(m_deposit >= 0, InvalidDeposit, ""); }
void setDeposit(int _deposit) { m_deposit = _deposit; assertThrow(m_deposit >= 0, InvalidDeposit, ""); }
/// Changes the source location used for each appended item.
void setSourceLocation(SourceLocation const& _location) { m_currentSourceLocation = _location; }
@ -118,7 +115,6 @@ protected:
/// returns the replaced tags.
std::map<u256, u256> optimiseInternal(bool _enable, bool _isCreation, size_t _runs);
void donePath() { if (m_totalDeposit != INT_MAX && m_totalDeposit != m_deposit) BOOST_THROW_EXCEPTION(InvalidDeposit()); }
unsigned bytesRequired(unsigned subTagSize) const;
private:
@ -141,8 +137,6 @@ protected:
mutable std::vector<size_t> m_tagPositionsInBytecode;
int m_deposit = 0;
int m_baseDeposit = 0;
int m_totalDeposit = 0;
SourceLocation m_currentSourceLocation;
};

View File

@ -148,6 +148,14 @@ private:
using AssemblyItems = std::vector<AssemblyItem>;
inline size_t bytesRequired(AssemblyItems const& _items, size_t _addressLength)
{
size_t size = 0;
for (AssemblyItem const& item: _items)
size += item.bytesRequired(_addressLength);
return size;
}
std::ostream& operator<<(std::ostream& _out, AssemblyItem const& _item);
inline std::ostream& operator<<(std::ostream& _out, AssemblyItems const& _items)
{

View File

@ -234,7 +234,7 @@ void CSECodeGenerator::addDependencies(Id _c)
if (expr.item && expr.item->type() == Operation && (
expr.item->instruction() == Instruction::SLOAD ||
expr.item->instruction() == Instruction::MLOAD ||
expr.item->instruction() == Instruction::SHA3
expr.item->instruction() == Instruction::KECCAK256
))
{
// this loads an unknown value from storage or memory and thus, in addition to its
@ -260,7 +260,7 @@ void CSECodeGenerator::addDependencies(Id _c)
case Instruction::MLOAD:
knownToBeIndependent = m_expressionClasses.knownToBeDifferentBy32(slot, slotToLoadFrom);
break;
case Instruction::SHA3:
case Instruction::KECCAK256:
{
Id length = expr.arguments.at(1);
AssemblyItem offsetInstr(Instruction::SUB, expr.item->location());

View File

@ -99,10 +99,7 @@ bigint ConstantOptimisationMethod::dataGas(bytes const& _data) const
size_t ConstantOptimisationMethod::bytesRequired(AssemblyItems const& _items)
{
size_t size = 0;
for (AssemblyItem const& item: _items)
size += item.bytesRequired(3); // assume 3 byte addresses
return size;
return eth::bytesRequired(_items, 3); // assume 3 byte addresses
}
void ConstantOptimisationMethod::replaceConstants(

View File

@ -32,8 +32,8 @@ struct EVMSchedule
unsigned stackLimit = 1024;
unsigned expGas = 10;
unsigned expByteGas = 10;
unsigned sha3Gas = 30;
unsigned sha3WordGas = 6;
unsigned keccak256Gas = 30;
unsigned keccak256WordGas = 6;
unsigned sloadGas = 200;
unsigned sstoreSetGas = 20000;
unsigned sstoreResetGas = 5000;

View File

@ -96,13 +96,14 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
classes.find(AssemblyItem(1))
}));
break;
case Instruction::SHA3:
gas = GasCosts::sha3Gas;
gas += wordGas(GasCosts::sha3WordGas, m_state->relativeStackElement(-1));
case Instruction::KECCAK256:
gas = GasCosts::keccak256Gas;
gas += wordGas(GasCosts::keccak256WordGas, m_state->relativeStackElement(-1));
gas += memoryGas(0, -1);
break;
case Instruction::CALLDATACOPY:
case Instruction::CODECOPY:
case Instruction::RETURNDATACOPY:
gas += memoryGas(0, -2);
gas += wordGas(GasCosts::copyGas, m_state->relativeStackElement(-2));
break;
@ -128,6 +129,7 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
case Instruction::CALL:
case Instruction::CALLCODE:
case Instruction::DELEGATECALL:
case Instruction::STATICCALL:
{
if (_includeExternalCosts)
// We assume that we do not know the target contract and thus, the consumption is infinite.
@ -141,8 +143,10 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
gas = GasConsumption::infinite();
if (_item.instruction() == Instruction::CALL)
gas += GasCosts::callNewAccountGas; // We very rarely know whether the address exists.
int valueSize = _item.instruction() == Instruction::DELEGATECALL ? 0 : 1;
if (!classes.knownZero(m_state->relativeStackElement(-1 - valueSize)))
int valueSize = 1;
if (_item.instruction() == Instruction::DELEGATECALL || _item.instruction() == Instruction::STATICCALL)
valueSize = 0;
else if (!classes.knownZero(m_state->relativeStackElement(-1 - valueSize)))
gas += GasCosts::callValueTransferGas;
gas += memoryGas(-2 - valueSize, -3 - valueSize);
gas += memoryGas(-4 - valueSize, -5 - valueSize);
@ -154,6 +158,7 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
gas += GasCosts::callNewAccountGas; // We very rarely know whether the address exists.
break;
case Instruction::CREATE:
case Instruction::CREATE2:
if (_includeExternalCosts)
// We assume that we do not know the target contract and thus, the consumption is infinite.
gas = GasConsumption::infinite();

View File

@ -48,8 +48,8 @@ namespace GasCosts
static unsigned const balanceGas = 400;
static unsigned const expGas = 10;
static unsigned const expByteGas = 50;
static unsigned const sha3Gas = 30;
static unsigned const sha3WordGas = 6;
static unsigned const keccak256Gas = 30;
static unsigned const keccak256WordGas = 6;
static unsigned const sloadGas = 200;
static unsigned const sstoreSetGas = 20000;
static unsigned const sstoreResetGas = 5000;

View File

@ -53,7 +53,7 @@ const std::map<std::string, Instruction> dev::solidity::c_instructions =
{ "ADDMOD", Instruction::ADDMOD },
{ "MULMOD", Instruction::MULMOD },
{ "SIGNEXTEND", Instruction::SIGNEXTEND },
{ "SHA3", Instruction::SHA3 },
{ "KECCAK256", Instruction::KECCAK256 },
{ "ADDRESS", Instruction::ADDRESS },
{ "BALANCE", Instruction::BALANCE },
{ "ORIGIN", Instruction::ORIGIN },
@ -67,6 +67,8 @@ const std::map<std::string, Instruction> dev::solidity::c_instructions =
{ "GASPRICE", Instruction::GASPRICE },
{ "EXTCODESIZE", Instruction::EXTCODESIZE },
{ "EXTCODECOPY", Instruction::EXTCODECOPY },
{ "RETURNDATASIZE", Instruction::RETURNDATASIZE },
{ "RETURNDATACOPY", Instruction::RETURNDATACOPY },
{ "BLOCKHASH", Instruction::BLOCKHASH },
{ "COINBASE", Instruction::COINBASE },
{ "TIMESTAMP", Instruction::TIMESTAMP },
@ -157,8 +159,10 @@ const std::map<std::string, Instruction> dev::solidity::c_instructions =
{ "CREATE", Instruction::CREATE },
{ "CALL", Instruction::CALL },
{ "CALLCODE", Instruction::CALLCODE },
{ "STATICCALL", Instruction::STATICCALL },
{ "RETURN", Instruction::RETURN },
{ "DELEGATECALL", Instruction::DELEGATECALL },
{ "CREATE2", Instruction::CREATE2 },
{ "REVERT", Instruction::REVERT },
{ "INVALID", Instruction::INVALID },
{ "SELFDESTRUCT", Instruction::SELFDESTRUCT }
@ -189,7 +193,7 @@ static const std::map<Instruction, InstructionInfo> c_instructionInfo =
{ 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::KECCAK256, { "KECCAK256", 0, 2, 1, false, Tier::Special } },
{ Instruction::ADDRESS, { "ADDRESS", 0, 0, 1, false, Tier::Base } },
{ Instruction::BALANCE, { "BALANCE", 0, 1, 1, false, Tier::Balance } },
{ Instruction::ORIGIN, { "ORIGIN", 0, 0, 1, false, Tier::Base } },
@ -203,6 +207,8 @@ static const std::map<Instruction, InstructionInfo> c_instructionInfo =
{ Instruction::GASPRICE, { "GASPRICE", 0, 0, 1, false, Tier::Base } },
{ Instruction::EXTCODESIZE, { "EXTCODESIZE", 0, 1, 1, false, Tier::ExtCode } },
{ Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0, true, Tier::ExtCode } },
{ Instruction::RETURNDATASIZE, {"RETURNDATASIZE", 0, 0, 1, false, Tier::Base } },
{ Instruction::RETURNDATACOPY, {"RETURNDATACOPY", 0, 3, 0, true, Tier::VeryLow } },
{ 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 } },
@ -210,7 +216,7 @@ static const std::map<Instruction, InstructionInfo> c_instructionInfo =
{ 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::MLOAD, { "MLOAD", 0, 1, 1, true, 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 } },
@ -295,7 +301,9 @@ static const std::map<Instruction, InstructionInfo> c_instructionInfo =
{ 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::STATICCALL, { "STATICCALL", 0, 6, 1, true, Tier::Special } },
{ Instruction::CREATE2, { "CREATE2", 0, 4, 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::Special } }
};

View File

@ -62,7 +62,7 @@ enum class Instruction: uint8_t
NOT, ///< bitwise NOT opertation
BYTE, ///< retrieve single byte from word
SHA3 = 0x20, ///< compute SHA3-256 hash
KECCAK256 = 0x20, ///< compute KECCAK-256 hash
ADDRESS = 0x30, ///< get address of currently executing account
BALANCE, ///< get balance of the given account
@ -77,6 +77,8 @@ enum class Instruction: uint8_t
GASPRICE, ///< get price of gas in current environment
EXTCODESIZE, ///< get external code size (from another contract)
EXTCODECOPY, ///< copy external code (from another contract)
RETURNDATASIZE = 0x3d, ///< get size of return data buffer
RETURNDATACOPY = 0x3e, ///< copy return data in current environment to memory
BLOCKHASH = 0x40, ///< get hash of most recent complete block
COINBASE, ///< get the block's coinbase address
@ -85,6 +87,13 @@ enum class Instruction: uint8_t
DIFFICULTY, ///< get the block's difficulty
GASLIMIT, ///< get the block's gas limit
JUMPTO = 0x4a, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp
JUMPIF, ///< conditionally alter the program counter -- not part of Instructions.cpp
JUMPV, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp
JUMPSUB, ///< alter the program counter to a beginsub -- not part of Instructions.cpp
JUMPSUBV, ///< alter the program counter to a beginsub -- not part of Instructions.cpp
RETURNSUB, ///< return to subroutine jumped from -- not part of Instructions.cpp
POP = 0x50, ///< remove item from stack
MLOAD, ///< load word from memory
MSTORE, ///< save word to memory
@ -97,6 +106,8 @@ enum class Instruction: uint8_t
MSIZE, ///< get the size of active memory
GAS, ///< get the amount of available gas
JUMPDEST, ///< set a potential jump destination
BEGINSUB, ///< set a potential jumpsub destination -- not part of Instructions.cpp
BEGINDATA, ///< begine the data section -- not part of Instructions.cpp
PUSH1 = 0x60, ///< place 1 byte item on stack
PUSH2, ///< place 2 byte item on stack
@ -176,6 +187,8 @@ 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
STATICCALL = 0xfa, ///< like CALL but disallow state modifications
CREATE2 = 0xfb, ///< create new account with associated code at address `sha3(sender + salt + sha3(init code)) % 2**160`
REVERT = 0xfd, ///< halt execution, revert state and return output data
INVALID = 0xfe, ///< invalid instruction for expressing runtime errors (e.g., division-by-zero)

View File

@ -136,10 +136,10 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
m_stackHeight + _item.deposit(),
loadFromMemory(arguments[0], _item.location())
);
else if (_item.instruction() == Instruction::SHA3)
else if (_item.instruction() == Instruction::KECCAK256)
setStackElement(
m_stackHeight + _item.deposit(),
applySha3(arguments.at(0), arguments.at(1), _item.location())
applyKeccak256(arguments.at(0), arguments.at(1), _item.location())
);
else
{
@ -346,18 +346,18 @@ ExpressionClasses::Id KnownState::loadFromMemory(Id _slot, SourceLocation const&
return m_memoryContent[_slot] = m_expressionClasses->find(item, {_slot}, true, m_sequenceNumber);
}
KnownState::Id KnownState::applySha3(
KnownState::Id KnownState::applyKeccak256(
Id _start,
Id _length,
SourceLocation const& _location
)
{
AssemblyItem sha3Item(Instruction::SHA3, _location);
AssemblyItem keccak256Item(Instruction::KECCAK256, _location);
// Special logic if length is a short constant, otherwise we cannot tell.
u256 const* l = m_expressionClasses->knownConstant(_length);
// unknown or too large length
if (!l || *l > 128)
return m_expressionClasses->find(sha3Item, {_start, _length}, true, m_sequenceNumber);
return m_expressionClasses->find(keccak256Item, {_start, _length}, true, m_sequenceNumber);
vector<Id> arguments;
for (u256 i = 0; i < *l; i += 32)
@ -368,10 +368,10 @@ KnownState::Id KnownState::applySha3(
);
arguments.push_back(loadFromMemory(slot, _location));
}
if (m_knownSha3Hashes.count(arguments))
return m_knownSha3Hashes.at(arguments);
if (m_knownKeccak256Hashes.count(arguments))
return m_knownKeccak256Hashes.at(arguments);
Id v;
// If all arguments are known constants, compute the sha3 here
// If all arguments are known constants, compute the Keccak-256 here
if (all_of(arguments.begin(), arguments.end(), [this](Id _a) { return !!m_expressionClasses->knownConstant(_a); }))
{
bytes data;
@ -381,8 +381,8 @@ KnownState::Id KnownState::applySha3(
v = m_expressionClasses->find(AssemblyItem(u256(dev::keccak256(data)), _location));
}
else
v = m_expressionClasses->find(sha3Item, {_start, _length}, true, m_sequenceNumber);
return m_knownSha3Hashes[arguments] = v;
v = m_expressionClasses->find(keccak256Item, {_start, _length}, true, m_sequenceNumber);
return m_knownKeccak256Hashes[arguments] = v;
}
set<u256> KnownState::tagsInExpression(KnownState::Id _expressionId)

View File

@ -150,8 +150,8 @@ private:
StoreOperation storeInMemory(Id _slot, Id _value, SourceLocation const& _location);
/// Retrieves the current value at the given slot in memory or creates a new special mload class.
Id loadFromMemory(Id _slot, SourceLocation const& _location);
/// Finds or creates a new expression that applies the sha3 hash function to the contents in memory.
Id applySha3(Id _start, Id _length, SourceLocation const& _location);
/// Finds or creates a new expression that applies the Keccak-256 hash function to the contents in memory.
Id applyKeccak256(Id _start, Id _length, SourceLocation const& _location);
/// @returns a new or already used Id representing the given set of tags.
Id tagUnion(std::set<u256> _tags);
@ -167,8 +167,8 @@ private:
/// Knowledge about memory content. Keys are memory addresses, note that the values overlap
/// and are not contained here if they are not completely known.
std::map<Id, Id> m_memoryContent;
/// Keeps record of all sha3 hashes that are computed.
std::map<std::vector<Id>, Id> m_knownSha3Hashes;
/// Keeps record of all Keccak-256 hashes that are computed.
std::map<std::vector<Id>, Id> m_knownKeccak256Hashes;
/// Structure containing the classes of equivalent expressions.
std::shared_ptr<ExpressionClasses> m_expressionClasses;
/// Container for unions of tags stored on the stack.

View File

@ -136,6 +136,21 @@ struct DoubleSwap: SimplePeepholeOptimizerMethod<DoubleSwap, 2>
}
};
struct DoublePush: SimplePeepholeOptimizerMethod<DoublePush, 2>
{
static bool applySimple(AssemblyItem const& _push1, AssemblyItem const& _push2, std::back_insert_iterator<AssemblyItems> _out)
{
if (_push1.type() == Push && _push2.type() == Push && _push1.data() == _push2.data())
{
*_out = _push1;
*_out = {Instruction::DUP1, _push2.location()};
return true;
}
else
return false;
}
};
struct JumpToNext: SimplePeepholeOptimizerMethod<JumpToNext, 3>
{
static size_t applySimple(
@ -235,13 +250,15 @@ bool PeepholeOptimiser::optimise()
{
OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)};
while (state.i < m_items.size())
applyMethods(state, PushPop(), OpPop(), DoubleSwap(), JumpToNext(), UnreachableCode(), TagConjunctions(), Identity());
if (m_optimisedItems.size() < m_items.size())
applyMethods(state, PushPop(), OpPop(), DoublePush(), DoubleSwap(), JumpToNext(), UnreachableCode(), TagConjunctions(), Identity());
if (m_optimisedItems.size() < m_items.size() || (
m_optimisedItems.size() == m_items.size() &&
eth::bytesRequired(m_optimisedItems, 3) < eth::bytesRequired(m_items, 3)
))
{
m_items = std::move(m_optimisedItems);
return true;
}
else
return false;
}

View File

@ -137,12 +137,16 @@ bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
case Instruction::CALL:
case Instruction::CALLCODE:
case Instruction::DELEGATECALL:
case Instruction::STATICCALL:
case Instruction::CREATE:
case Instruction::CREATE2:
case Instruction::GAS:
case Instruction::PC:
case Instruction::MSIZE: // depends on previous writes and reads, not only on content
case Instruction::BALANCE: // depends on previous calls
case Instruction::EXTCODESIZE:
case Instruction::RETURNDATACOPY: // depends on previous calls
case Instruction::RETURNDATASIZE:
return false;
default:
return true;
@ -156,11 +160,13 @@ bool SemanticInformation::invalidatesMemory(Instruction _instruction)
case Instruction::CALLDATACOPY:
case Instruction::CODECOPY:
case Instruction::EXTCODECOPY:
case Instruction::RETURNDATACOPY:
case Instruction::MSTORE:
case Instruction::MSTORE8:
case Instruction::CALL:
case Instruction::CALLCODE:
case Instruction::DELEGATECALL:
case Instruction::STATICCALL:
return true;
default:
return false;
@ -175,6 +181,7 @@ bool SemanticInformation::invalidatesStorage(Instruction _instruction)
case Instruction::CALLCODE:
case Instruction::DELEGATECALL:
case Instruction::CREATE:
case Instruction::CREATE2:
case Instruction::SSTORE:
return true;
default:

View File

@ -103,7 +103,7 @@ Rules::Rules()
{{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::LT, {A, B}}, [=]() -> u256 { return A.d() < B.d() ? 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; }},
@ -124,23 +124,26 @@ Rules::Rules()
return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask);
}},
// invariants involving known constants
// invariants involving known constants (commutative instructions will be checked with swapped operants too)
{{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::MUL, {X, 1}}, [=]{ return X; }},
{{Instruction::DIV, {X, 0}}, [=]{ return u256(0); }},
{{Instruction::DIV, {0, X}}, [=]{ return u256(0); }},
{{Instruction::DIV, {X, 1}}, [=]{ return X; }},
{{Instruction::SDIV, {X, 0}}, [=]{ return u256(0); }},
{{Instruction::SDIV, {0, X}}, [=]{ return u256(0); }},
{{Instruction::SDIV, {X, 1}}, [=]{ return X; }},
{{Instruction::AND, {X, ~u256(0)}}, [=]{ return X; }},
{{Instruction::AND, {X, 0}}, [=]{ return u256(0); }},
{{Instruction::OR, {X, 0}}, [=]{ return X; }},
{{Instruction::OR, {X, ~u256(0)}}, [=]{ return ~u256(0); }},
{{Instruction::XOR, {X, 0}}, [=]{ return X; }},
{{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; }},
@ -153,6 +156,7 @@ Rules::Rules()
{{Instruction::SGT, {X, X}}, [=]{ return u256(0); }},
{{Instruction::MOD, {X, X}}, [=]{ return u256(0); }},
// logical instruction combinations
{{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; }},
@ -160,6 +164,7 @@ Rules::Rules()
{{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,
@ -172,14 +177,17 @@ Rules::Rules()
{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>()},
@ -210,6 +218,7 @@ Rules::Rules()
[=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }
}});
}
// move constants across subtractions
addRules(vector<pair<Pattern, function<Pattern()>>>{
{

View File

@ -0,0 +1,117 @@
/*
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/>.
*/
/**
* @date 2017
* Abstract assembly interface, subclasses of which are to be used with the generic
* bytecode generator.
*/
#pragma once
#include <libdevcore/CommonData.h>
#include <functional>
namespace dev
{
struct SourceLocation;
namespace solidity
{
enum class Instruction: uint8_t;
namespace assembly
{
struct Instruction;
struct Identifier;
}
}
namespace julia
{
///
/// Assembly class that abstracts both the libevmasm assembly and the new julia evm assembly.
///
class AbstractAssembly
{
public:
using LabelID = size_t;
virtual ~AbstractAssembly() {}
/// Set a new source location valid starting from the next instruction.
virtual void setSourceLocation(SourceLocation const& _location) = 0;
/// Retrieve the current height of the stack. This does not have to be zero
/// at the beginning.
virtual int stackHeight() const = 0;
/// Append an EVM instruction.
virtual void appendInstruction(solidity::Instruction _instruction) = 0;
/// Append a constant.
virtual void appendConstant(u256 const& _constant) = 0;
/// Append a label.
virtual void appendLabel(LabelID _labelId) = 0;
/// Append a label reference.
virtual void appendLabelReference(LabelID _labelId) = 0;
/// Generate a new unique label.
virtual LabelID newLabelId() = 0;
/// Append a reference to a to-be-linked symobl.
/// Currently, we assume that the value is always a 20 byte number.
virtual void appendLinkerSymbol(std::string const& _name) = 0;
/// Append a jump instruction.
/// @param _stackDiffAfter the stack adjustment after this instruction.
/// This is helpful to stack height analysis if there is no continuing control flow.
virtual void appendJump(int _stackDiffAfter) = 0;
/// Append a jump-to-immediate operation.
/// @param _stackDiffAfter the stack adjustment after this instruction.
virtual void appendJumpTo(LabelID _labelId, int _stackDiffAfter = 0) = 0;
/// Append a jump-to-if-immediate operation.
virtual void appendJumpToIf(LabelID _labelId) = 0;
/// Start a subroutine identified by @a _labelId that takes @a _arguments
/// stack slots as arguments.
virtual void appendBeginsub(LabelID _labelId, int _arguments) = 0;
/// Call a subroutine identified by @a _labelId, taking @a _arguments from the
/// stack upon call and putting @a _returns arguments onto the stack upon return.
virtual void appendJumpsub(LabelID _labelId, int _arguments, int _returns) = 0;
/// Return from a subroutine.
/// @param _stackDiffAfter the stack adjustment after this instruction.
virtual void appendReturnsub(int _returns, int _stackDiffAfter = 0) = 0;
/// Append the assembled size as a constant.
virtual void appendAssemblySize() = 0;
};
enum class IdentifierContext { LValue, RValue };
/// Object that is used to resolve references and generate code for access to identifiers external
/// to inline assembly (not used in standalone assembly mode).
struct ExternalIdentifierAccess
{
using Resolver = std::function<size_t(solidity::assembly::Identifier const&, IdentifierContext, bool /*_crossesFunctionBoundary*/)>;
/// Resolve a an external reference given by the identifier in the given context.
/// @returns the size of the value (number of stack slots) or size_t(-1) if not found.
Resolver resolve;
using CodeGenerator = std::function<void(solidity::assembly::Identifier const&, IdentifierContext, julia::AbstractAssembly&)>;
/// Generate code for retrieving the value (rvalue context) or storing the value (lvalue context)
/// of an identifier. The code should be appended to the assembly. In rvalue context, the value is supposed
/// to be put onto the stack, in lvalue context, the value is assumed to be at the top of the stack.
CodeGenerator generateCode;
};
}
}

View File

@ -0,0 +1,194 @@
/*
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/>.
*/
/**
* Assembly interface for EVM and EVM1.5.
*/
#include <libjulia/backends/evm/EVMAssembly.h>
#include <libevmasm/Instruction.h>
#include <libsolidity/interface/Exceptions.h>
using namespace std;
using namespace dev;
using namespace julia;
namespace
{
/// Size of labels in bytes. Four-byte labels are required by some EVM1.5 instructions.
size_t constexpr labelReferenceSize = 4;
size_t constexpr assemblySizeReferenceSize = 4;
}
void EVMAssembly::setSourceLocation(SourceLocation const&)
{
// Ignored for now;
}
void EVMAssembly::appendInstruction(solidity::Instruction _instr)
{
m_bytecode.push_back(byte(_instr));
m_stackHeight += solidity::instructionInfo(_instr).ret - solidity::instructionInfo(_instr).args;
}
void EVMAssembly::appendConstant(u256 const& _constant)
{
bytes data = toCompactBigEndian(_constant, 1);
appendInstruction(solidity::pushInstruction(data.size()));
m_bytecode += data;
}
void EVMAssembly::appendLabel(LabelID _labelId)
{
setLabelToCurrentPosition(_labelId);
appendInstruction(solidity::Instruction::JUMPDEST);
}
void EVMAssembly::appendLabelReference(LabelID _labelId)
{
solAssert(!m_evm15, "Cannot use plain label references in EMV1.5 mode.");
// @TODO we now always use labelReferenceSize for all labels, it could be shortened
// for some of them.
appendInstruction(solidity::pushInstruction(labelReferenceSize));
m_labelReferences[m_bytecode.size()] = _labelId;
m_bytecode += bytes(labelReferenceSize);
}
EVMAssembly::LabelID EVMAssembly::newLabelId()
{
m_labelPositions[m_nextLabelId] = size_t(-1);
return m_nextLabelId++;
}
void EVMAssembly::appendLinkerSymbol(string const&)
{
solAssert(false, "Linker symbols not yet implemented.");
}
void EVMAssembly::appendJump(int _stackDiffAfter)
{
solAssert(!m_evm15, "Plain JUMP used for EVM 1.5");
appendInstruction(solidity::Instruction::JUMP);
m_stackHeight += _stackDiffAfter;
}
void EVMAssembly::appendJumpTo(LabelID _labelId, int _stackDiffAfter)
{
if (m_evm15)
{
m_bytecode.push_back(byte(solidity::Instruction::JUMPTO));
appendLabelReferenceInternal(_labelId);
m_stackHeight += _stackDiffAfter;
}
else
{
appendLabelReference(_labelId);
appendJump(_stackDiffAfter);
}
}
void EVMAssembly::appendJumpToIf(LabelID _labelId)
{
if (m_evm15)
{
m_bytecode.push_back(byte(solidity::Instruction::JUMPIF));
appendLabelReferenceInternal(_labelId);
m_stackHeight--;
}
else
{
appendLabelReference(_labelId);
appendInstruction(solidity::Instruction::JUMPI);
}
}
void EVMAssembly::appendBeginsub(LabelID _labelId, int _arguments)
{
solAssert(m_evm15, "BEGINSUB used for EVM 1.0");
solAssert(_arguments >= 0, "");
setLabelToCurrentPosition(_labelId);
m_bytecode.push_back(byte(solidity::Instruction::BEGINSUB));
m_stackHeight += _arguments;
}
void EVMAssembly::appendJumpsub(LabelID _labelId, int _arguments, int _returns)
{
solAssert(m_evm15, "JUMPSUB used for EVM 1.0");
solAssert(_arguments >= 0 && _returns >= 0, "");
m_bytecode.push_back(byte(solidity::Instruction::JUMPSUB));
appendLabelReferenceInternal(_labelId);
m_stackHeight += _returns - _arguments;
}
void EVMAssembly::appendReturnsub(int _returns, int _stackDiffAfter)
{
solAssert(m_evm15, "RETURNSUB used for EVM 1.0");
solAssert(_returns >= 0, "");
m_bytecode.push_back(byte(solidity::Instruction::RETURNSUB));
m_stackHeight += _stackDiffAfter - _returns;
}
eth::LinkerObject EVMAssembly::finalize()
{
size_t bytecodeSize = m_bytecode.size();
for (auto const& ref: m_assemblySizePositions)
updateReference(ref, assemblySizeReferenceSize, u256(bytecodeSize));
for (auto const& ref: m_labelReferences)
{
size_t referencePos = ref.first;
solAssert(m_labelPositions.count(ref.second), "");
size_t labelPos = m_labelPositions.at(ref.second);
solAssert(labelPos != size_t(-1), "Undefined but allocated label used.");
updateReference(referencePos, labelReferenceSize, u256(labelPos));
}
eth::LinkerObject obj;
obj.bytecode = m_bytecode;
return obj;
}
void EVMAssembly::setLabelToCurrentPosition(LabelID _labelId)
{
solAssert(m_labelPositions.count(_labelId), "Label not found.");
solAssert(m_labelPositions[_labelId] == size_t(-1), "Label already set.");
m_labelPositions[_labelId] = m_bytecode.size();
}
void EVMAssembly::appendLabelReferenceInternal(LabelID _labelId)
{
m_labelReferences[m_bytecode.size()] = _labelId;
m_bytecode += bytes(labelReferenceSize);
}
void EVMAssembly::appendAssemblySize()
{
appendInstruction(solidity::pushInstruction(assemblySizeReferenceSize));
m_assemblySizePositions.push_back(m_bytecode.size());
m_bytecode += bytes(assemblySizeReferenceSize);
}
void EVMAssembly::updateReference(size_t pos, size_t size, u256 value)
{
solAssert(m_bytecode.size() >= size && pos <= m_bytecode.size() - size, "");
solAssert(value < (u256(1) << (8 * size)), "");
for (size_t i = 0; i < size; i++)
m_bytecode[pos + i] = byte((value >> (8 * (size - i - 1))) & 0xff);
}

View File

@ -0,0 +1,94 @@
/*
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/>.
*/
/**
* Assembly interface for EVM and EVM1.5.
*/
#pragma once
#include <libjulia/backends/evm/AbstractAssembly.h>
#include <libevmasm/LinkerObject.h>
#include <map>
namespace dev
{
namespace julia
{
class EVMAssembly: public AbstractAssembly
{
public:
explicit EVMAssembly(bool _evm15 = false): m_evm15(_evm15) { }
virtual ~EVMAssembly() {}
/// Set a new source location valid starting from the next instruction.
virtual void setSourceLocation(SourceLocation const& _location) override;
/// Retrieve the current height of the stack. This does not have to be zero
/// at the beginning.
virtual int stackHeight() const override { return m_stackHeight; }
/// Append an EVM instruction.
virtual void appendInstruction(solidity::Instruction _instruction) override;
/// Append a constant.
virtual void appendConstant(u256 const& _constant) override;
/// Append a label.
virtual void appendLabel(LabelID _labelId) override;
/// Append a label reference.
virtual void appendLabelReference(LabelID _labelId) override;
/// Generate a new unique label.
virtual LabelID newLabelId() override;
/// Append a reference to a to-be-linked symobl.
/// Currently, we assume that the value is always a 20 byte number.
virtual void appendLinkerSymbol(std::string const& _name) override;
/// Append a jump instruction.
/// @param _stackDiffAfter the stack adjustment after this instruction.
virtual void appendJump(int _stackDiffAfter) override;
/// Append a jump-to-immediate operation.
virtual void appendJumpTo(LabelID _labelId, int _stackDiffAfter) override;
/// Append a jump-to-if-immediate operation.
virtual void appendJumpToIf(LabelID _labelId) override;
/// Start a subroutine.
virtual void appendBeginsub(LabelID _labelId, int _arguments) override;
/// Call a subroutine.
virtual void appendJumpsub(LabelID _labelId, int _arguments, int _returns) override;
/// Return from a subroutine.
virtual void appendReturnsub(int _returns, int _stackDiffAfter) override;
/// Append the assembled size as a constant.
virtual void appendAssemblySize() override;
/// Resolves references inside the bytecode and returns the linker object.
eth::LinkerObject finalize();
private:
void setLabelToCurrentPosition(AbstractAssembly::LabelID _labelId);
void appendLabelReferenceInternal(AbstractAssembly::LabelID _labelId);
void updateReference(size_t pos, size_t size, u256 value);
bool m_evm15 = false; ///< if true, switch to evm1.5 mode
LabelID m_nextLabelId = 0;
int m_stackHeight = 0;
bytes m_bytecode;
std::map<LabelID, size_t> m_labelPositions;
std::map<size_t, LabelID> m_labelReferences;
std::vector<size_t> m_assemblySizePositions;
};
}
}

View File

@ -0,0 +1,500 @@
/*
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/>.
*/
/**
* Common code generator for translating Julia / inline assembly to EVM and EVM1.5.
*/
#include <libjulia/backends/evm/EVMCodeTransform.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/interface/Exceptions.h>
#include <boost/range/adaptor/reversed.hpp>
using namespace std;
using namespace dev;
using namespace dev::julia;
using namespace dev::solidity;
using namespace dev::solidity::assembly;
void CodeTransform::operator()(VariableDeclaration const& _varDecl)
{
solAssert(m_scope, "");
int expectedItems = _varDecl.variables.size();
int height = m_assembly.stackHeight();
boost::apply_visitor(*this, *_varDecl.value);
expectDeposit(expectedItems, height);
for (auto const& variable: _varDecl.variables)
{
auto& var = boost::get<Scope::Variable>(m_scope->identifiers.at(variable.name));
m_context->variableStackHeights[&var] = height++;
}
checkStackHeight(&_varDecl);
}
void CodeTransform::operator()(Assignment const& _assignment)
{
visitExpression(*_assignment.value);
m_assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName);
checkStackHeight(&_assignment);
}
void CodeTransform::operator()(StackAssignment const& _assignment)
{
m_assembly.setSourceLocation(_assignment.location);
generateAssignment(_assignment.variableName);
checkStackHeight(&_assignment);
}
void CodeTransform::operator()(Label const& _label)
{
m_assembly.setSourceLocation(_label.location);
solAssert(m_scope, "");
solAssert(m_scope->identifiers.count(_label.name), "");
Scope::Label& label = boost::get<Scope::Label>(m_scope->identifiers.at(_label.name));
m_assembly.appendLabel(labelID(label));
checkStackHeight(&_label);
}
void CodeTransform::operator()(FunctionCall const& _call)
{
solAssert(m_scope, "");
m_assembly.setSourceLocation(_call.location);
EVMAssembly::LabelID returnLabel(-1); // only used for evm 1.0
if (!m_evm15)
{
returnLabel = m_assembly.newLabelId();
m_assembly.appendLabelReference(returnLabel);
m_stackAdjustment++;
}
Scope::Function* function = nullptr;
solAssert(m_scope->lookup(_call.functionName.name, Scope::NonconstVisitor(
[=](Scope::Variable&) { solAssert(false, "Expected function name."); },
[=](Scope::Label&) { solAssert(false, "Expected function name."); },
[&](Scope::Function& _function) { function = &_function; }
)), "Function name not found.");
solAssert(function, "");
solAssert(function->arguments.size() == _call.arguments.size(), "");
for (auto const& arg: _call.arguments | boost::adaptors::reversed)
visitExpression(arg);
m_assembly.setSourceLocation(_call.location);
if (m_evm15)
m_assembly.appendJumpsub(functionEntryID(*function), function->arguments.size(), function->returns.size());
else
{
m_assembly.appendJumpTo(functionEntryID(*function), function->returns.size() - function->arguments.size() - 1);
m_assembly.appendLabel(returnLabel);
m_stackAdjustment--;
}
checkStackHeight(&_call);
}
void CodeTransform::operator()(FunctionalInstruction const& _instruction)
{
if (m_evm15 && (
_instruction.instruction.instruction == solidity::Instruction::JUMP ||
_instruction.instruction.instruction == solidity::Instruction::JUMPI
))
{
bool const isJumpI = _instruction.instruction.instruction == solidity::Instruction::JUMPI;
if (isJumpI)
{
solAssert(_instruction.arguments.size() == 2, "");
visitExpression(_instruction.arguments.at(1));
}
else
{
solAssert(_instruction.arguments.size() == 1, "");
}
m_assembly.setSourceLocation(_instruction.location);
auto label = labelFromIdentifier(boost::get<assembly::Identifier>(_instruction.arguments.at(0)));
if (isJumpI)
m_assembly.appendJumpToIf(label);
else
m_assembly.appendJumpTo(label);
}
else
{
for (auto const& arg: _instruction.arguments | boost::adaptors::reversed)
visitExpression(arg);
(*this)(_instruction.instruction);
}
checkStackHeight(&_instruction);
}
void CodeTransform::operator()(assembly::Identifier const& _identifier)
{
m_assembly.setSourceLocation(_identifier.location);
// First search internals, then externals.
solAssert(m_scope, "");
if (m_scope->lookup(_identifier.name, Scope::NonconstVisitor(
[=](Scope::Variable& _var)
{
if (int heightDiff = variableHeightDiff(_var, false))
m_assembly.appendInstruction(solidity::dupInstruction(heightDiff));
else
// Store something to balance the stack
m_assembly.appendConstant(u256(0));
},
[=](Scope::Label& _label)
{
m_assembly.appendLabelReference(labelID(_label));
},
[=](Scope::Function&)
{
solAssert(false, "Function not removed during desugaring.");
}
)))
{
return;
}
solAssert(
m_identifierAccess.generateCode,
"Identifier not found and no external access available."
);
m_identifierAccess.generateCode(_identifier, IdentifierContext::RValue, m_assembly);
checkStackHeight(&_identifier);
}
void CodeTransform::operator()(assembly::Literal const& _literal)
{
m_assembly.setSourceLocation(_literal.location);
if (_literal.kind == assembly::LiteralKind::Number)
m_assembly.appendConstant(u256(_literal.value));
else if (_literal.kind == assembly::LiteralKind::Boolean)
{
if (_literal.value == "true")
m_assembly.appendConstant(u256(1));
else
m_assembly.appendConstant(u256(0));
}
else
{
solAssert(_literal.value.size() <= 32, "");
m_assembly.appendConstant(u256(h256(_literal.value, h256::FromBinary, h256::AlignLeft)));
}
checkStackHeight(&_literal);
}
void CodeTransform::operator()(assembly::Instruction const& _instruction)
{
solAssert(!m_evm15 || _instruction.instruction != solidity::Instruction::JUMP, "Bare JUMP instruction used for EVM1.5");
solAssert(!m_evm15 || _instruction.instruction != solidity::Instruction::JUMPI, "Bare JUMPI instruction used for EVM1.5");
m_assembly.setSourceLocation(_instruction.location);
m_assembly.appendInstruction(_instruction.instruction);
checkStackHeight(&_instruction);
}
void CodeTransform::operator()(Switch const& _switch)
{
//@TODO use JUMPV in EVM1.5?
visitExpression(*_switch.expression);
int expressionHeight = m_assembly.stackHeight();
map<Case const*, AbstractAssembly::LabelID> caseBodies;
AbstractAssembly::LabelID end = m_assembly.newLabelId();
for (Case const& c: _switch.cases)
{
if (c.value)
{
(*this)(*c.value);
m_assembly.setSourceLocation(c.location);
AbstractAssembly::LabelID bodyLabel = m_assembly.newLabelId();
caseBodies[&c] = bodyLabel;
solAssert(m_assembly.stackHeight() == expressionHeight + 1, "");
m_assembly.appendInstruction(solidity::dupInstruction(2));
m_assembly.appendInstruction(solidity::Instruction::EQ);
m_assembly.appendJumpToIf(bodyLabel);
}
else
// default case
(*this)(c.body);
}
m_assembly.setSourceLocation(_switch.location);
m_assembly.appendJumpTo(end);
size_t numCases = caseBodies.size();
for (auto const& c: caseBodies)
{
m_assembly.setSourceLocation(c.first->location);
m_assembly.appendLabel(c.second);
(*this)(c.first->body);
// Avoid useless "jump to next" for the last case.
if (--numCases > 0)
{
m_assembly.setSourceLocation(c.first->location);
m_assembly.appendJumpTo(end);
}
}
m_assembly.setSourceLocation(_switch.location);
m_assembly.appendLabel(end);
m_assembly.appendInstruction(solidity::Instruction::POP);
checkStackHeight(&_switch);
}
void CodeTransform::operator()(FunctionDefinition const& _function)
{
solAssert(m_scope, "");
solAssert(m_scope->identifiers.count(_function.name), "");
Scope::Function& function = boost::get<Scope::Function>(m_scope->identifiers.at(_function.name));
int const localStackAdjustment = m_evm15 ? 0 : 1;
int height = localStackAdjustment;
solAssert(m_info.scopes.at(&_function.body), "");
Scope* varScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get();
solAssert(varScope, "");
for (auto const& v: _function.arguments | boost::adaptors::reversed)
{
auto& var = boost::get<Scope::Variable>(varScope->identifiers.at(v.name));
m_context->variableStackHeights[&var] = height++;
}
m_assembly.setSourceLocation(_function.location);
int stackHeightBefore = m_assembly.stackHeight();
AbstractAssembly::LabelID afterFunction = m_assembly.newLabelId();
if (m_evm15)
{
m_assembly.appendJumpTo(afterFunction, -stackHeightBefore);
m_assembly.appendBeginsub(functionEntryID(function), _function.arguments.size());
}
else
{
m_assembly.appendJumpTo(afterFunction, -stackHeightBefore + height);
m_assembly.appendLabel(functionEntryID(function));
}
m_stackAdjustment += localStackAdjustment;
for (auto const& v: _function.returns)
{
auto& var = boost::get<Scope::Variable>(varScope->identifiers.at(v.name));
m_context->variableStackHeights[&var] = height++;
// Preset stack slots for return variables to zero.
m_assembly.appendConstant(u256(0));
}
CodeTransform(m_assembly, m_info, m_julia, m_evm15, m_identifierAccess, localStackAdjustment, m_context)
(_function.body);
{
// The stack layout here is:
// <return label>? <arguments...> <return values...>
// But we would like it to be:
// <return values...> <return label>?
// So we have to append some SWAP and POP instructions.
// This vector holds the desired target positions of all stack slots and is
// modified parallel to the actual stack.
vector<int> stackLayout;
if (!m_evm15)
stackLayout.push_back(_function.returns.size()); // Move return label to the top
stackLayout += vector<int>(_function.arguments.size(), -1); // discard all arguments
for (size_t i = 0; i < _function.returns.size(); ++i)
stackLayout.push_back(i); // Move return values down, but keep order.
solAssert(stackLayout.size() <= 17, "Stack too deep");
while (!stackLayout.empty() && stackLayout.back() != int(stackLayout.size() - 1))
if (stackLayout.back() < 0)
{
m_assembly.appendInstruction(solidity::Instruction::POP);
stackLayout.pop_back();
}
else
{
m_assembly.appendInstruction(swapInstruction(stackLayout.size() - stackLayout.back() - 1));
swap(stackLayout[stackLayout.back()], stackLayout.back());
}
for (int i = 0; size_t(i) < stackLayout.size(); ++i)
solAssert(i == stackLayout[i], "Error reshuffling stack.");
}
if (m_evm15)
m_assembly.appendReturnsub(_function.returns.size(), stackHeightBefore);
else
m_assembly.appendJump(stackHeightBefore - _function.returns.size());
m_stackAdjustment -= localStackAdjustment;
m_assembly.appendLabel(afterFunction);
checkStackHeight(&_function);
}
void CodeTransform::operator()(ForLoop const& _forLoop)
{
Scope* originalScope = m_scope;
// We start with visiting the block, but not finalizing it.
m_scope = m_info.scopes.at(&_forLoop.pre).get();
int stackStartHeight = m_assembly.stackHeight();
visitStatements(_forLoop.pre.statements);
// TODO: When we implement break and continue, the labels and the stack heights at that point
// have to be stored in a stack.
AbstractAssembly::LabelID loopStart = m_assembly.newLabelId();
AbstractAssembly::LabelID loopEnd = m_assembly.newLabelId();
AbstractAssembly::LabelID postPart = m_assembly.newLabelId();
m_assembly.setSourceLocation(_forLoop.location);
m_assembly.appendLabel(loopStart);
visitExpression(*_forLoop.condition);
m_assembly.setSourceLocation(_forLoop.location);
m_assembly.appendInstruction(solidity::Instruction::ISZERO);
m_assembly.appendJumpToIf(loopEnd);
(*this)(_forLoop.body);
m_assembly.setSourceLocation(_forLoop.location);
m_assembly.appendLabel(postPart);
(*this)(_forLoop.post);
m_assembly.setSourceLocation(_forLoop.location);
m_assembly.appendJumpTo(loopStart);
m_assembly.appendLabel(loopEnd);
finalizeBlock(_forLoop.pre, stackStartHeight);
m_scope = originalScope;
}
void CodeTransform::operator()(Block const& _block)
{
Scope* originalScope = m_scope;
m_scope = m_info.scopes.at(&_block).get();
int blockStartStackHeight = m_assembly.stackHeight();
visitStatements(_block.statements);
finalizeBlock(_block, blockStartStackHeight);
m_scope = originalScope;
}
AbstractAssembly::LabelID CodeTransform::labelFromIdentifier(Identifier const& _identifier)
{
AbstractAssembly::LabelID label = AbstractAssembly::LabelID(-1);
if (!m_scope->lookup(_identifier.name, Scope::NonconstVisitor(
[=](Scope::Variable&) { solAssert(false, "Expected label"); },
[&](Scope::Label& _label)
{
label = labelID(_label);
},
[=](Scope::Function&) { solAssert(false, "Expected label"); }
)))
{
solAssert(false, "Identifier not found.");
}
return label;
}
AbstractAssembly::LabelID CodeTransform::labelID(Scope::Label const& _label)
{
if (!m_context->labelIDs.count(&_label))
m_context->labelIDs[&_label] = m_assembly.newLabelId();
return m_context->labelIDs[&_label];
}
AbstractAssembly::LabelID CodeTransform::functionEntryID(Scope::Function const& _function)
{
if (!m_context->functionEntryIDs.count(&_function))
m_context->functionEntryIDs[&_function] = m_assembly.newLabelId();
return m_context->functionEntryIDs[&_function];
}
void CodeTransform::visitExpression(Statement const& _expression)
{
int height = m_assembly.stackHeight();
boost::apply_visitor(*this, _expression);
expectDeposit(1, height);
}
void CodeTransform::visitStatements(vector<Statement> const& _statements)
{
for (auto const& statement: _statements)
boost::apply_visitor(*this, statement);
}
void CodeTransform::finalizeBlock(Block const& _block, int blockStartStackHeight)
{
m_assembly.setSourceLocation(_block.location);
// pop variables
solAssert(m_info.scopes.at(&_block).get() == m_scope, "");
for (size_t i = 0; i < m_scope->numberOfVariables(); ++i)
m_assembly.appendInstruction(solidity::Instruction::POP);
int deposit = m_assembly.stackHeight() - blockStartStackHeight;
solAssert(deposit == 0, "Invalid stack height at end of block.");
checkStackHeight(&_block);
}
void CodeTransform::generateAssignment(Identifier const& _variableName)
{
solAssert(m_scope, "");
auto var = m_scope->lookup(_variableName.name);
if (var)
{
Scope::Variable const& _var = boost::get<Scope::Variable>(*var);
if (int heightDiff = variableHeightDiff(_var, true))
m_assembly.appendInstruction(solidity::swapInstruction(heightDiff - 1));
m_assembly.appendInstruction(solidity::Instruction::POP);
}
else
{
solAssert(
m_identifierAccess.generateCode,
"Identifier not found and no external access available."
);
m_identifierAccess.generateCode(_variableName, IdentifierContext::LValue, m_assembly);
}
}
int CodeTransform::variableHeightDiff(solidity::assembly::Scope::Variable const& _var, bool _forSwap)
{
solAssert(m_context->variableStackHeights.count(&_var), "");
int heightDiff = m_assembly.stackHeight() - m_context->variableStackHeights[&_var];
if (heightDiff <= (_forSwap ? 1 : 0) || heightDiff > (_forSwap ? 17 : 16))
{
solUnimplemented(
"Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")"
);
return 0;
}
else
return heightDiff;
}
void CodeTransform::expectDeposit(int _deposit, int _oldHeight)
{
solAssert(m_assembly.stackHeight() == _oldHeight + _deposit, "Invalid stack deposit.");
}
void CodeTransform::checkStackHeight(void const* _astElement)
{
solAssert(m_info.stackHeightInfo.count(_astElement), "Stack height for AST element not found.");
solAssert(
m_info.stackHeightInfo.at(_astElement) == m_assembly.stackHeight() - m_stackAdjustment,
"Stack height mismatch between analysis and code generation phase: Analysis: " +
to_string(m_info.stackHeightInfo.at(_astElement)) +
" code gen: " +
to_string(m_assembly.stackHeight() - m_stackAdjustment)
);
}

View File

@ -0,0 +1,149 @@
/*
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/>.
*/
/**
* Common code generator for translating Julia / inline assembly to EVM and EVM1.5.
*/
#include <libjulia/backends/evm/EVMAssembly.h>
#include <libsolidity/inlineasm/AsmScope.h>
#include <libsolidity/inlineasm/AsmDataForward.h>
#include <boost/variant.hpp>
#include <boost/optional.hpp>
namespace dev
{
namespace solidity
{
class ErrorReporter;
namespace assembly
{
struct AsmAnalysisInfo;
}
}
namespace julia
{
class EVMAssembly;
class CodeTransform: public boost::static_visitor<>
{
public:
/// Create the code transformer.
/// @param _identifierAccess used to resolve identifiers external to the inline assembly
CodeTransform(
julia::AbstractAssembly& _assembly,
solidity::assembly::AsmAnalysisInfo& _analysisInfo,
bool _julia = false,
bool _evm15 = false,
ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess()
): CodeTransform(
_assembly,
_analysisInfo,
_julia,
_evm15,
_identifierAccess,
_assembly.stackHeight(),
std::make_shared<Context>()
)
{
}
protected:
struct Context
{
using Scope = solidity::assembly::Scope;
std::map<Scope::Label const*, AbstractAssembly::LabelID> labelIDs;
std::map<Scope::Function const*, AbstractAssembly::LabelID> functionEntryIDs;
std::map<Scope::Variable const*, int> variableStackHeights;
};
CodeTransform(
julia::AbstractAssembly& _assembly,
solidity::assembly::AsmAnalysisInfo& _analysisInfo,
bool _julia,
bool _evm15,
ExternalIdentifierAccess const& _identifierAccess,
int _stackAdjustment,
std::shared_ptr<Context> _context
):
m_assembly(_assembly),
m_info(_analysisInfo),
m_julia(_julia),
m_evm15(_evm15),
m_identifierAccess(_identifierAccess),
m_stackAdjustment(_stackAdjustment),
m_context(_context)
{}
public:
void operator()(solidity::assembly::Instruction const& _instruction);
void operator()(solidity::assembly::Literal const& _literal);
void operator()(solidity::assembly::Identifier const& _identifier);
void operator()(solidity::assembly::FunctionalInstruction const& _instr);
void operator()(solidity::assembly::FunctionCall const&);
void operator()(solidity::assembly::Label const& _label);
void operator()(solidity::assembly::StackAssignment const& _assignment);
void operator()(solidity::assembly::Assignment const& _assignment);
void operator()(solidity::assembly::VariableDeclaration const& _varDecl);
void operator()(solidity::assembly::Switch const& _switch);
void operator()(solidity::assembly::FunctionDefinition const&);
void operator()(solidity::assembly::ForLoop const&);
void operator()(solidity::assembly::Block const& _block);
private:
AbstractAssembly::LabelID labelFromIdentifier(solidity::assembly::Identifier const& _identifier);
/// @returns the label ID corresponding to the given label, allocating a new one if
/// necessary.
AbstractAssembly::LabelID labelID(solidity::assembly::Scope::Label const& _label);
AbstractAssembly::LabelID functionEntryID(solidity::assembly::Scope::Function const& _function);
/// Generates code for an expression that is supposed to return a single value.
void visitExpression(solidity::assembly::Statement const& _expression);
void visitStatements(std::vector<solidity::assembly::Statement> const& _statements);
/// Pops all variables declared in the block and checks that the stack height is equal
/// to @a _blackStartStackHeight.
void finalizeBlock(solidity::assembly::Block const& _block, int _blockStartStackHeight);
void generateAssignment(solidity::assembly::Identifier const& _variableName);
/// Determines the stack height difference to the given variables. Throws
/// if it is not yet in scope or the height difference is too large. Returns
/// the (positive) stack height difference otherwise.
int variableHeightDiff(solidity::assembly::Scope::Variable const& _var, bool _forSwap);
void expectDeposit(int _deposit, int _oldHeight);
void checkStackHeight(void const* _astElement);
julia::AbstractAssembly& m_assembly;
solidity::assembly::AsmAnalysisInfo& m_info;
solidity::assembly::Scope* m_scope = nullptr;
bool m_julia = false;
bool m_evm15 = false;
ExternalIdentifierAccess m_identifierAccess;
/// Adjustment between the stack height as determined during the analysis phase
/// and the stack height in the assembly. This is caused by an initial stack being present
/// for inline assembly and different stack heights depending on the EVM backend used
/// (EVM 1.0 or 1.5).
int m_stackAdjustment = 0;
std::shared_ptr<Context> m_context;
};
}
}

View File

@ -1,6 +0,0 @@
#pragma once
#include "CodeFragment.h"
#include "Compiler.h"
#include "CompilerState.h"
#include "Parser.h"

View File

@ -171,11 +171,23 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
return string();
};
auto varAddress = [&](string const& n)
auto varAddress = [&](string const& n, bool createMissing = false)
{
if (n.empty())
error<InvalidName>("Empty variable name not allowed");
auto it = _s.vars.find(n);
if (it == _s.vars.end())
error<InvalidName>(std::string("Symbol not found: ") + s);
{
if (createMissing)
{
// Create new variable
bool ok;
tie(it, ok) = _s.vars.insert(make_pair(n, make_pair(_s.stackSize, 32)));
_s.stackSize += 32;
}
else
error<InvalidName>(std::string("Symbol not found: ") + n);
}
return it->second.first;
};
@ -192,7 +204,13 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
{
if (_t.size() != 2)
error<IncorrectParameterCount>();
m_asm.append(CodeFragment::compile(contentsString(firstAsString()), _s).m_asm);
string fileName = firstAsString();
if (fileName.empty())
error<InvalidName>("Empty file name provided");
string contents = contentsString(fileName);
if (contents.empty())
error<InvalidName>(std::string("File not found (or empty): ") + fileName);
m_asm.append(CodeFragment::compile(contents, _s).m_asm);
}
else if (us == "SET")
{
@ -202,7 +220,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
for (auto const& i: _t)
if (c++ == 2)
m_asm.append(CodeFragment(i, _s, false).m_asm);
m_asm.append((u256)varAddress(firstAsString()));
m_asm.append((u256)varAddress(firstAsString(), true));
m_asm.append(Instruction::MSTORE);
}
else if (us == "GET")
@ -240,7 +258,11 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
}
else if (ii == 2)
if (_t.size() == 3)
_s.defs[n] = CodeFragment(i, _s);
{
/// NOTE: some compilers could do the assignment first if this is done in a single line
CodeFragment code = CodeFragment(i, _s);
_s.defs[n] = code;
}
else
for (auto const& j: i)
{
@ -439,15 +461,21 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
int minDep = min(code[1].m_asm.deposit(), code[2].m_asm.deposit());
m_asm.append(code[0].m_asm);
auto pos = m_asm.appendJumpI();
m_asm.onePath();
auto mainBranch = m_asm.appendJumpI();
/// The else branch.
int startDeposit = m_asm.deposit();
m_asm.append(code[2].m_asm, minDep);
auto end = m_asm.appendJump();
m_asm.otherPath();
m_asm << pos.tag();
int deposit = m_asm.deposit();
m_asm.setDeposit(startDeposit);
/// The main branch.
m_asm << mainBranch.tag();
m_asm.append(code[1].m_asm, minDep);
m_asm << end.tag();
m_asm.donePaths();
if (m_asm.deposit() != deposit)
error<InvalidDeposit>();
}
else if (us == "WHEN" || us == "UNLESS")
{
@ -458,11 +486,8 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
if (us == "WHEN")
m_asm.append(Instruction::ISZERO);
auto end = m_asm.appendJumpI();
m_asm.onePath();
m_asm.otherPath();
m_asm.append(code[1].m_asm, 0);
m_asm << end.tag();
m_asm.donePaths();
}
else if (us == "WHILE" || us == "UNTIL")
{
@ -515,8 +540,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
requireMaxSize(3);
requireDeposit(1, 1);
auto subPush = m_asm.newSub(make_shared<Assembly>(code[0].assembly(ns)));
m_asm.append(m_asm.newPushSubSize(subPush.data()));
auto subPush = m_asm.appendSubroutine(make_shared<Assembly>(code[0].assembly(ns)));
m_asm.append(Instruction::DUP1);
if (code.size() == 3)
{
@ -571,11 +595,9 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
{
for (auto const& i: code)
m_asm.append(i.m_asm);
m_asm.popTo(1);
}
else if (us == "PANIC")
{
m_asm.appendJump(m_asm.errorTag());
// Leave only the last item on stack.
while (m_asm.deposit() > 1)
m_asm.append(Instruction::POP);
}
else if (us == "BYTECODESIZE")
{

View File

@ -69,10 +69,11 @@ std::string dev::eth::compileLLLToAsm(std::string const& _src, bool _opt, std::v
{
CompilerState cs;
cs.populateStandard();
string ret = CodeFragment::compile(_src, cs).assembly(cs).optimise(_opt).out();
stringstream ret;
CodeFragment::compile(_src, cs).assembly(cs).optimise(_opt).stream(ret);
for (auto i: cs.treesToKill)
killBigints(i);
return ret;
return ret.str();
}
catch (Exception const& _e)
{

View File

@ -45,26 +45,29 @@ CodeFragment const& CompilerState::getDef(std::string const& _s)
void CompilerState::populateStandard()
{
static const string s = "{"
"(def 'panic () (asm INVALID))"
"(def 'allgas (- (gas) 21))"
"(def 'send (to value) (call allgas to value 0 0 0 0))"
"(def 'send (gaslimit to value) (call gaslimit to value 0 0 0 0))"
"(def 'msg (gaslimit to value data datasize outsize) { (set x outsize) (set y (alloc @32)) (call gaslimit to value data datasize @0 @32) @0 })"
// NOTE: in this macro, memory location 0 is set in order to force msize to be at least 32 bytes.
"(def 'msg (gaslimit to value data datasize outsize) { [0]:0 [0]:(msize) (call gaslimit to value data datasize @0 outsize) @0 })"
"(def 'msg (gaslimit to value data datasize) { (call gaslimit to value data datasize 0 32) @0 })"
"(def 'msg (gaslimit to value data) { [0]:data (msg gaslimit to value 0 32) })"
"(def 'msg (to value data) { [0]:data (msg allgas to value 0 32) })"
"(def 'msg (to data) { [0]:data (msg allgas to 0 0 32) })"
"(def 'create (value code) { [0]:(msize) (create value @0 (lll code @0)) })"
"(def 'create (code) { [0]:(msize) (create 0 @0 (lll code @0)) })"
// NOTE: in the create macros, memory location 0 is set in order to force msize to be at least 32 bytes.
"(def 'create (value code) { [0]:0 [0]:(msize) (create value @0 (lll code @0)) })"
"(def 'create (code) { [0]:0 [0]:(msize) (create 0 @0 (lll code @0)) })"
"(def 'sha3 (loc len) (keccak256 loc len))"
"(def 'sha3 (val) { [0]:val (sha3 0 32) })"
"(def 'sha3pair (a b) { [0]:a [32]:b (sha3 0 64) })"
"(def 'sha3trip (a b c) { [0]:a [32]:b [64]:c (sha3 0 96) })"
"(def 'keccak256 (loc len) (sha3 loc len))"
"(def 'return (val) { [0]:val (return 0 32) })"
"(def 'returnlll (code) (return 0 (lll code 0)) )"
"(def 'makeperm (name pos) { (def name (sload pos)) (def name (v) (sstore pos v)) } )"
"(def 'permcount 0)"
"(def 'perm (name) { (makeperm name permcount) (def 'permcount (+ permcount 1)) } )"
"(def 'ecrecover (r s v hash) { [0] r [32] s [64] v [96] hash (msg allgas 1 0 0 128) })"
"(def 'ecrecover (hash v r s) { [0] hash [32] v [64] r [96] s (msg allgas 1 0 0 128) })"
"(def 'sha256 (data datasize) (msg allgas 2 0 data datasize))"
"(def 'ripemd160 (data datasize) (msg allgas 3 0 data datasize))"
"(def 'sha256 (val) { [0]:val (sha256 0 32) })"
@ -73,6 +76,9 @@ void CompilerState::populateStandard()
"(def 'szabo 1000000000000)"
"(def 'finney 1000000000000000)"
"(def 'ether 1000000000000000000)"
// these could be replaced by native instructions once supported by EVM
"(def 'shl (val shift) (mul val (exp 2 shift)))"
"(def 'shr (val shift) (div val (exp 2 shift)))"
"}";
CodeFragment::compile(s, *this);
}

View File

@ -109,7 +109,7 @@ void dev::eth::parseTreeLLL(string const& _s, sp::utree& o_out)
qi::rule<it, space_type, sp::utree::list_type()> mstore = '[' > element > ']' > -qi::lit(":") > element;
qi::rule<it, space_type, sp::utree::list_type()> sstore = qi::lit("[[") > element > qi::lit("]]") > -qi::lit(":") > element;
qi::rule<it, space_type, sp::utree::list_type()> calldataload = qi::lit("$") > element;
qi::rule<it, space_type, sp::utree::list_type()> list = '(' > +element > ')';
qi::rule<it, space_type, sp::utree::list_type()> list = '(' > *element > ')';
qi::rule<it, space_type, sp::utree()> extra = sload[tagNode<2>()] | mload[tagNode<1>()] | sstore[tagNode<4>()] | mstore[tagNode<3>()] | seq[tagNode<5>()] | calldataload[tagNode<6>()];
element = atom | list | extra;

View File

@ -7,10 +7,12 @@ aux_source_directory(formal SRC_LIST)
aux_source_directory(interface SRC_LIST)
aux_source_directory(parsing SRC_LIST)
aux_source_directory(inlineasm SRC_LIST)
# Until we have a clear separation, libjulia has to be included here
aux_source_directory(../libjulia SRC_LIST)
set(EXECUTABLE solidity)
file(GLOB HEADERS "*/*.h")
file(GLOB HEADERS "*/*.h" "../libjulia/backends/evm/*")
include_directories(BEFORE ..)
add_library(${EXECUTABLE} ${SRC_LIST} ${HEADERS})

View File

@ -23,6 +23,7 @@
#include <libsolidity/analysis/DocStringAnalyser.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <libsolidity/parsing/DocStringParser.h>
using namespace std;
@ -39,7 +40,7 @@ bool DocStringAnalyser::analyseDocStrings(SourceUnit const& _sourceUnit)
bool DocStringAnalyser::visit(ContractDefinition const& _node)
{
static const set<string> validTags = set<string>{"author", "title", "dev", "notice", "why3"};
static const set<string> validTags = set<string>{"author", "title", "dev", "notice"};
parseDocStrings(_node, _node.annotation(), validTags, "contracts");
return true;
@ -65,16 +66,6 @@ bool DocStringAnalyser::visit(EventDefinition const& _node)
return true;
}
bool DocStringAnalyser::visitNode(ASTNode const& _node)
{
if (auto node = dynamic_cast<Statement const*>(&_node))
{
static const set<string> validTags = {"why3"};
parseDocStrings(*node, node->annotation(), validTags, "statements");
}
return true;
}
void DocStringAnalyser::handleCallable(
CallableDeclaration const& _callable,
Documented const& _node,
@ -110,7 +101,7 @@ void DocStringAnalyser::parseDocStrings(
DocStringParser parser;
if (_node.documentation() && !_node.documentation()->empty())
{
if (!parser.parse(*_node.documentation(), m_errors))
if (!parser.parse(*_node.documentation(), m_errorReporter))
m_errorOccured = true;
_annotation.docTags = parser.tags();
}
@ -121,8 +112,6 @@ void DocStringAnalyser::parseDocStrings(
void DocStringAnalyser::appendError(string const& _description)
{
auto err = make_shared<Error>(Error::Type::DocstringParsingError);
*err << errinfo_comment(_description);
m_errors.push_back(err);
m_errorOccured = true;
m_errorReporter.docstringParsingError(_description);
}

View File

@ -30,6 +30,8 @@ namespace dev
namespace solidity
{
class ErrorReporter;
/**
* Parses and analyses the doc strings.
* Stores the parsing results in the AST annotations and reports errors.
@ -37,7 +39,7 @@ namespace solidity
class DocStringAnalyser: private ASTConstVisitor
{
public:
DocStringAnalyser(ErrorList& _errors): m_errors(_errors) {}
DocStringAnalyser(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
bool analyseDocStrings(SourceUnit const& _sourceUnit);
private:
@ -46,8 +48,6 @@ private:
virtual bool visit(ModifierDefinition const& _modifier) override;
virtual bool visit(EventDefinition const& _event) override;
virtual bool visitNode(ASTNode const&) override;
void handleCallable(
CallableDeclaration const& _callable,
Documented const& _node,
@ -64,7 +64,7 @@ private:
void appendError(std::string const& _description);
bool m_errorOccured = false;
ErrorList& m_errors;
ErrorReporter& m_errorReporter;
};
}

View File

@ -24,7 +24,9 @@
#include <libsolidity/ast/AST.h>
#include <libsolidity/analysis/TypeChecker.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <boost/algorithm/string.hpp>
using namespace std;
@ -36,10 +38,10 @@ namespace solidity
NameAndTypeResolver::NameAndTypeResolver(
vector<Declaration const*> const& _globals,
map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes,
ErrorList& _errors
ErrorReporter& _errorReporter
) :
m_scopes(_scopes),
m_errors(_errors)
m_errorReporter(_errorReporter)
{
if (!m_scopes[nullptr])
m_scopes[nullptr].reset(new DeclarationContainer());
@ -52,11 +54,11 @@ bool NameAndTypeResolver::registerDeclarations(ASTNode& _sourceUnit, ASTNode con
// The helper registers all declarations in m_scopes as a side-effect of its construction.
try
{
DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errors, _currentScope);
DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errorReporter, _currentScope);
}
catch (FatalError const&)
{
if (m_errors.empty())
if (m_errorReporter.errors().empty())
throw; // Something is weird here, rather throw again.
return false;
}
@ -73,7 +75,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
string const& path = imp->annotation().absolutePath;
if (!_sourceUnits.count(path))
{
reportDeclarationError(
m_errorReporter.declarationError(
imp->location(),
"Import \"" + path + "\" (referenced as \"" + imp->path() + "\") not found."
);
@ -88,7 +90,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
auto declarations = scope->second->resolveName(alias.first->name(), false);
if (declarations.empty())
{
reportDeclarationError(
m_errorReporter.declarationError(
imp->location(),
"Declaration \"" +
alias.first->name() +
@ -106,7 +108,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
ASTString const* name = alias.second ? alias.second.get() : &declaration->name();
if (!target.registerDeclaration(*declaration, name))
{
reportDeclarationError(
m_errorReporter.declarationError(
imp->location(),
"Identifier \"" + *name + "\" already declared."
);
@ -119,7 +121,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
for (auto const& declaration: nameAndDeclaration.second)
if (!target.registerDeclaration(*declaration, &nameAndDeclaration.first))
{
reportDeclarationError(
m_errorReporter.declarationError(
imp->location(),
"Identifier \"" + nameAndDeclaration.first + "\" already declared."
);
@ -137,7 +139,7 @@ bool NameAndTypeResolver::resolveNamesAndTypes(ASTNode& _node, bool _resolveInsi
}
catch (FatalError const&)
{
if (m_errors.empty())
if (m_errorReporter.errors().empty())
throw; // Something is weird here, rather throw again.
return false;
}
@ -152,7 +154,7 @@ bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration)
}
catch (FatalError const&)
{
if (m_errors.empty())
if (m_errorReporter.errors().empty())
throw; // Something is weird here, rather throw again.
return false;
}
@ -214,7 +216,7 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
for (auto parameter: functionType->parameterTypes() + functionType->returnParameterTypes())
if (!parameter)
reportFatalDeclarationError(_identifier.location(), "Function type can not be used in this context.");
m_errorReporter.fatalDeclarationError(_identifier.location(), "Function type can not be used in this context.");
if (uniqueFunctions.end() == find_if(
uniqueFunctions.begin(),
@ -232,6 +234,26 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
return uniqueFunctions;
}
void NameAndTypeResolver::warnVariablesNamedLikeInstructions()
{
for (auto const& instruction: c_instructions)
{
string const instructionName{boost::algorithm::to_lower_copy(instruction.first)};
auto declarations = nameFromCurrentScope(instructionName);
for (Declaration const* const declaration: declarations)
{
solAssert(!!declaration, "");
if (dynamic_cast<MagicVariableDeclaration const* const>(declaration))
// Don't warn the user for what the user did not.
continue;
m_errorReporter.warning(
declaration->location(),
"Variable is shadowed in inline assembly by an instruction of the same name"
);
}
}
}
bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode)
{
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(&_node))
@ -290,7 +312,7 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res
{
if (m_scopes.count(&_node))
m_currentScope = m_scopes[&_node].get();
return ReferencesResolver(m_errors, *this, _resolveInsideCode).resolve(_node);
return ReferencesResolver(m_errorReporter, *this, _resolveInsideCode).resolve(_node);
}
}
@ -328,11 +350,10 @@ void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base)
secondDeclarationLocation = declaration->location();
}
reportDeclarationError(
m_errorReporter.declarationError(
secondDeclarationLocation,
"Identifier already declared.",
firstDeclarationLocation,
"The previous declaration is here:"
SecondarySourceLocation().append("The previous declaration is here:", firstDeclarationLocation),
"Identifier already declared."
);
}
}
@ -347,19 +368,19 @@ void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract)
UserDefinedTypeName const& baseName = baseSpecifier->name();
auto base = dynamic_cast<ContractDefinition const*>(baseName.annotation().referencedDeclaration);
if (!base)
reportFatalTypeError(baseName.createTypeError("Contract expected."));
m_errorReporter.fatalTypeError(baseName.location(), "Contract expected.");
// "push_front" has the effect that bases mentioned later can overwrite members of bases
// mentioned earlier
input.back().push_front(base);
vector<ContractDefinition const*> const& basesBases = base->annotation().linearizedBaseContracts;
if (basesBases.empty())
reportFatalTypeError(baseName.createTypeError("Definition of base has to precede definition of derived contract"));
m_errorReporter.fatalTypeError(baseName.location(), "Definition of base has to precede definition of derived contract");
input.push_front(list<ContractDefinition const*>(basesBases.begin(), basesBases.end()));
}
input.back().push_front(&_contract);
vector<ContractDefinition const*> result = cThreeMerge(input);
if (result.empty())
reportFatalTypeError(_contract.createTypeError("Linearization of inheritance graph impossible"));
m_errorReporter.fatalTypeError(_contract.location(), "Linearization of inheritance graph impossible");
_contract.annotation().linearizedBaseContracts = result;
_contract.annotation().contractDependencies.insert(result.begin() + 1, result.end());
}
@ -415,61 +436,15 @@ vector<_T const*> NameAndTypeResolver::cThreeMerge(list<list<_T const*>>& _toMer
return result;
}
void NameAndTypeResolver::reportDeclarationError(
SourceLocation _sourceLoction,
string const& _description,
SourceLocation _secondarySourceLocation,
string const& _secondaryDescription
)
{
auto err = make_shared<Error>(Error::Type::DeclarationError); // todo remove Error?
*err <<
errinfo_sourceLocation(_sourceLoction) <<
errinfo_comment(_description) <<
errinfo_secondarySourceLocation(
SecondarySourceLocation().append(_secondaryDescription, _secondarySourceLocation)
);
m_errors.push_back(err);
}
void NameAndTypeResolver::reportDeclarationError(SourceLocation _sourceLocation, string const& _description)
{
auto err = make_shared<Error>(Error::Type::DeclarationError); // todo remove Error?
*err << errinfo_sourceLocation(_sourceLocation) << errinfo_comment(_description);
m_errors.push_back(err);
}
void NameAndTypeResolver::reportFatalDeclarationError(
SourceLocation _sourceLocation,
string const& _description
)
{
reportDeclarationError(_sourceLocation, _description);
BOOST_THROW_EXCEPTION(FatalError());
}
void NameAndTypeResolver::reportTypeError(Error const& _e)
{
m_errors.push_back(make_shared<Error>(_e));
}
void NameAndTypeResolver::reportFatalTypeError(Error const& _e)
{
reportTypeError(_e);
BOOST_THROW_EXCEPTION(FatalError());
}
DeclarationRegistrationHelper::DeclarationRegistrationHelper(
map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes,
ASTNode& _astRoot,
ErrorList& _errors,
ErrorReporter& _errorReporter,
ASTNode const* _currentScope
):
m_scopes(_scopes),
m_currentScope(_currentScope),
m_errors(_errors)
m_errorReporter(_errorReporter)
{
_astRoot.accept(*this);
solAssert(m_currentScope == _currentScope, "Scopes not correctly closed.");
@ -633,11 +608,10 @@ void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaratio
secondDeclarationLocation = _declaration.location();
}
declarationError(
m_errorReporter.declarationError(
secondDeclarationLocation,
"Identifier already declared.",
firstDeclarationLocation,
"The previous declaration is here:"
SecondarySourceLocation().append("The previous declaration is here:", firstDeclarationLocation),
"Identifier already declared."
);
}
@ -665,40 +639,5 @@ string DeclarationRegistrationHelper::currentCanonicalName() const
return ret;
}
void DeclarationRegistrationHelper::declarationError(
SourceLocation _sourceLocation,
string const& _description,
SourceLocation _secondarySourceLocation,
string const& _secondaryDescription
)
{
auto err = make_shared<Error>(Error::Type::DeclarationError);
*err <<
errinfo_sourceLocation(_sourceLocation) <<
errinfo_comment(_description) <<
errinfo_secondarySourceLocation(
SecondarySourceLocation().append(_secondaryDescription, _secondarySourceLocation)
);
m_errors.push_back(err);
}
void DeclarationRegistrationHelper::declarationError(SourceLocation _sourceLocation, string const& _description)
{
auto err = make_shared<Error>(Error::Type::DeclarationError);
*err << errinfo_sourceLocation(_sourceLocation) << errinfo_comment(_description);
m_errors.push_back(err);
}
void DeclarationRegistrationHelper::fatalDeclarationError(
SourceLocation _sourceLocation,
string const& _description
)
{
declarationError(_sourceLocation, _description);
BOOST_THROW_EXCEPTION(FatalError());
}
}
}

View File

@ -35,6 +35,8 @@ namespace dev
namespace solidity
{
class ErrorReporter;
/**
* Resolves name references, typenames and sets the (explicitly given) types for all variable
* declarations.
@ -48,7 +50,7 @@ public:
NameAndTypeResolver(
std::vector<Declaration const*> const& _globals,
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes,
ErrorList& _errors
ErrorReporter& _errorReporter
);
/// Registers all declarations found in the AST node, usually a source unit.
/// @returns false in case of error.
@ -88,6 +90,9 @@ public:
std::vector<Declaration const*> const& _declarations
);
/// Generate and store warnings about variables that are named like instructions.
void warnVariablesNamedLikeInstructions();
private:
/// Internal version of @a resolveNamesAndTypes (called from there) throws exceptions on fatal errors.
bool resolveNamesAndTypesInternal(ASTNode& _node, bool _resolveInsideCode = true);
@ -103,24 +108,6 @@ private:
template <class _T>
static std::vector<_T const*> cThreeMerge(std::list<std::list<_T const*>>& _toMerge);
// creates the Declaration error and adds it in the errors list
void reportDeclarationError(
SourceLocation _sourceLoction,
std::string const& _description,
SourceLocation _secondarySourceLocation,
std::string const& _secondaryDescription
);
// creates the Declaration error and adds it in the errors list
void reportDeclarationError(SourceLocation _sourceLocation, std::string const& _description);
// creates the Declaration error and adds it in the errors list and throws FatalError
void reportFatalDeclarationError(SourceLocation _sourceLocation, std::string const& _description);
// creates the Declaration error and adds it in the errors list
void reportTypeError(Error const& _e);
// creates the Declaration error and adds it in the errors list and throws FatalError
void reportFatalTypeError(Error const& _e);
/// Maps nodes declaring a scope to scopes, i.e. ContractDefinition and FunctionDeclaration,
/// where nullptr denotes the global scope. Note that structs are not scope since they do
/// not contain code.
@ -128,7 +115,7 @@ private:
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& m_scopes;
DeclarationContainer* m_currentScope = nullptr;
ErrorList& m_errors;
ErrorReporter& m_errorReporter;
};
/**
@ -145,7 +132,7 @@ public:
DeclarationRegistrationHelper(
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes,
ASTNode& _astRoot,
ErrorList& _errors,
ErrorReporter& _errorReporter,
ASTNode const* _currentScope = nullptr
);
@ -175,23 +162,11 @@ private:
/// @returns the canonical name of the current scope.
std::string currentCanonicalName() const;
// creates the Declaration error and adds it in the errors list
void declarationError(
SourceLocation _sourceLocation,
std::string const& _description,
SourceLocation _secondarySourceLocation,
std::string const& _secondaryDescription
);
// creates the Declaration error and adds it in the errors list
void declarationError(SourceLocation _sourceLocation, std::string const& _description);
// creates the Declaration error and adds it in the errors list and throws FatalError
void fatalDeclarationError(SourceLocation _sourceLocation, std::string const& _description);
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& m_scopes;
ASTNode const* m_currentScope = nullptr;
VariableScope* m_currentFunction = nullptr;
ErrorList& m_errors;
ErrorReporter& m_errorReporter;
};
}

View File

@ -18,6 +18,7 @@
#include <libsolidity/analysis/PostTypeChecker.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/analysis/SemVerHandler.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <libsolidity/interface/Version.h>
#include <boost/range/adaptor/map.hpp>
@ -32,17 +33,7 @@ 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);
return Error::containsOnlyWarnings(m_errorReporter.errors());
}
bool PostTypeChecker::visit(ContractDefinition const&)
@ -57,7 +48,11 @@ 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() + ".");
m_errorReporter.typeError(
declaration->location(),
"The value of the constant " + declaration->name() +
" has a cyclic dependency via " + identifier->name() + "."
);
m_constVariables.clear();
m_constVariableDependencies.clear();

View File

@ -28,6 +28,8 @@ namespace dev
namespace solidity
{
class ErrorReporter;
/**
* 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
@ -37,7 +39,7 @@ 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) {}
PostTypeChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
bool check(ASTNode const& _astRoot);
@ -55,10 +57,10 @@ private:
VariableDeclaration const* findCycle(
VariableDeclaration const* _startingFrom,
std::set<VariableDeclaration const*> const& _seen = {}
std::set<VariableDeclaration const*> const& _seen = std::set<VariableDeclaration const*>{}
);
ErrorList& m_errors;
ErrorReporter& m_errorReporter;
VariableDeclaration const* m_currentConstVariable = nullptr;
std::vector<VariableDeclaration const*> m_constVariables; ///< Required for determinism.

View File

@ -28,6 +28,7 @@
#include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <boost/algorithm/string.hpp>
@ -111,7 +112,7 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
case VariableDeclaration::Visibility::External:
break;
default:
typeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\".");
fatalTypeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\".");
}
if (_typeName.isPayable() && _typeName.visibility() != VariableDeclaration::Visibility::External)
@ -161,13 +162,16 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName)
bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
{
m_resolver.warnVariablesNamedLikeInstructions();
// Errors created in this stage are completely ignored because we do not yet know
// the type and size of external identifiers, which would result in false errors.
// The only purpose of this step is to fill the inline assembly annotation with
// external references.
ErrorList errorsIgnored;
assembly::ExternalIdentifierAccess::Resolver resolver =
[&](assembly::Identifier const& _identifier, assembly::IdentifierContext) {
ErrorList errors;
ErrorReporter errorsIgnored(errors);
julia::ExternalIdentifierAccess::Resolver resolver =
[&](assembly::Identifier const& _identifier, julia::IdentifierContext, bool _crossesFunctionBoundary) {
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name);
bool isSlot = boost::algorithm::ends_with(_identifier.name, "_slot");
bool isOffset = boost::algorithm::ends_with(_identifier.name, "_offset");
@ -186,6 +190,12 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
}
if (declarations.size() != 1)
return size_t(-1);
if (auto var = dynamic_cast<VariableDeclaration const*>(declarations.front()))
if (var->isLocalVariable() && _crossesFunctionBoundary)
{
declarationError(_identifier.location, "Cannot access local Solidity variables from inside an inline assembly function.");
return size_t(-1);
}
_inlineAssembly.annotation().externalReferences[&_identifier].isSlot = isSlot;
_inlineAssembly.annotation().externalReferences[&_identifier].isOffset = isOffset;
_inlineAssembly.annotation().externalReferences[&_identifier].declaration = declarations.front();
@ -194,7 +204,7 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
// Will be re-generated later with correct information
assembly::AsmAnalysisInfo analysisInfo;
assembly::AsmAnalyzer(analysisInfo, errorsIgnored, resolver).analyze(_inlineAssembly.operations());
assembly::AsmAnalyzer(analysisInfo, errorsIgnored, false, resolver).analyze(_inlineAssembly.operations());
return false;
}
@ -303,29 +313,25 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
void ReferencesResolver::typeError(SourceLocation const& _location, string const& _description)
{
auto err = make_shared<Error>(Error::Type::TypeError);
*err << errinfo_sourceLocation(_location) << errinfo_comment(_description);
m_errorOccurred = true;
m_errors.push_back(err);
m_errorReporter.typeError(_location, _description);
}
void ReferencesResolver::fatalTypeError(SourceLocation const& _location, string const& _description)
{
typeError(_location, _description);
BOOST_THROW_EXCEPTION(FatalError());
m_errorOccurred = true;
m_errorReporter.fatalTypeError(_location, _description);
}
void ReferencesResolver::declarationError(SourceLocation const& _location, string const& _description)
{
auto err = make_shared<Error>(Error::Type::DeclarationError);
*err << errinfo_sourceLocation(_location) << errinfo_comment(_description);
m_errorOccurred = true;
m_errors.push_back(err);
m_errorReporter.declarationError(_location, _description);
}
void ReferencesResolver::fatalDeclarationError(SourceLocation const& _location, string const& _description)
{
declarationError(_location, _description);
BOOST_THROW_EXCEPTION(FatalError());
m_errorOccurred = true;
m_errorReporter.fatalDeclarationError(_location, _description);
}

View File

@ -33,6 +33,7 @@ namespace dev
namespace solidity
{
class ErrorReporter;
class NameAndTypeResolver;
/**
@ -43,11 +44,11 @@ class ReferencesResolver: private ASTConstVisitor
{
public:
ReferencesResolver(
ErrorList& _errors,
ErrorReporter& _errorReporter,
NameAndTypeResolver& _resolver,
bool _resolveInsideCode = false
):
m_errors(_errors),
m_errorReporter(_errorReporter),
m_resolver(_resolver),
m_resolveInsideCode(_resolveInsideCode)
{}
@ -74,16 +75,16 @@ private:
/// Adds a new error to the list of errors.
void typeError(SourceLocation const& _location, std::string const& _description);
/// Adds a new error to the list of errors and throws to abort type checking.
/// Adds a new error to the list of errors and throws to abort reference resolving.
void fatalTypeError(SourceLocation const& _location, std::string const& _description);
/// Adds a new error to the list of errors.
void declarationError(const SourceLocation& _location, std::string const& _description);
void declarationError(SourceLocation const& _location, std::string const& _description);
/// Adds a new error to the list of errors and throws to abort type checking.
void fatalDeclarationError(const SourceLocation& _location, std::string const& _description);
/// Adds a new error to the list of errors and throws to abort reference resolving.
void fatalDeclarationError(SourceLocation const& _location, std::string const& _description);
ErrorList& m_errors;
ErrorReporter& m_errorReporter;
NameAndTypeResolver& m_resolver;
/// Stack of return parameters.
std::vector<ParameterList const*> m_returnParameters;

View File

@ -21,18 +21,18 @@
*/
#include <libsolidity/analysis/StaticAnalyzer.h>
#include <memory>
#include <libsolidity/ast/AST.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <memory>
using namespace std;
using namespace dev;
using namespace dev::solidity;
bool StaticAnalyzer::analyze(SourceUnit const& _sourceUnit)
{
_sourceUnit.accept(*this);
return Error::containsOnlyWarnings(m_errors);
return Error::containsOnlyWarnings(m_errorReporter.errors());
}
bool StaticAnalyzer::visit(ContractDefinition const& _contract)
@ -63,7 +63,7 @@ void StaticAnalyzer::endVisit(FunctionDefinition const&)
m_nonPayablePublic = false;
for (auto const& var: m_localVarUseCount)
if (var.second == 0)
warning(var.first->location(), "Unused local variable");
m_errorReporter.warning(var.first->location(), "Unused local variable");
m_localVarUseCount.clear();
}
@ -105,7 +105,11 @@ bool StaticAnalyzer::visit(Return const& _return)
bool StaticAnalyzer::visit(ExpressionStatement const& _statement)
{
if (_statement.expression().annotation().isPure)
warning(_statement.location(), "Statement has no effect.");
m_errorReporter.warning(
_statement.location(),
"Statement has no effect."
);
return true;
}
@ -114,17 +118,36 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)
if (m_nonPayablePublic && !m_library)
if (MagicType const* type = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type.get()))
if (type->kind() == MagicType::Kind::Message && _memberAccess.memberName() == "value")
warning(_memberAccess.location(), "\"msg.value\" used in non-payable function. Do you want to add the \"payable\" modifier to this function?");
m_errorReporter.warning(
_memberAccess.location(),
"\"msg.value\" used in non-payable function. Do you want to add the \"payable\" modifier to this function?"
);
if (_memberAccess.memberName() == "callcode")
if (auto const* type = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type.get()))
if (type->kind() == FunctionType::Kind::BareCallCode)
m_errorReporter.warning(
_memberAccess.location(),
"\"callcode\" has been deprecated in favour of \"delegatecall\"."
);
return true;
}
void StaticAnalyzer::warning(SourceLocation const& _location, string const& _description)
bool StaticAnalyzer::visit(InlineAssembly const& _inlineAssembly)
{
auto err = make_shared<Error>(Error::Type::Warning);
*err <<
errinfo_sourceLocation(_location) <<
errinfo_comment(_description);
if (!m_currentFunction)
return true;
m_errors.push_back(err);
for (auto const& ref: _inlineAssembly.annotation().externalReferences)
{
if (auto var = dynamic_cast<VariableDeclaration const*>(ref.second.declaration))
{
solAssert(!var->name().empty(), "");
if (var->isLocalVariable())
m_localVarUseCount[var] += 1;
}
}
return true;
}

View File

@ -44,15 +44,13 @@ class StaticAnalyzer: private ASTConstVisitor
{
public:
/// @param _errors the reference to the list of errors and warnings to add them found during static analysis.
explicit StaticAnalyzer(ErrorList& _errors): m_errors(_errors) {}
explicit StaticAnalyzer(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
/// Performs static analysis on the given source unit and all of its sub-nodes.
/// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings
bool analyze(SourceUnit const& _sourceUnit);
private:
/// Adds a new warning to the list of errors.
void warning(SourceLocation const& _location, std::string const& _description);
virtual bool visit(ContractDefinition const& _contract) override;
virtual void endVisit(ContractDefinition const& _contract) override;
@ -65,8 +63,9 @@ private:
virtual bool visit(Identifier const& _identifier) override;
virtual bool visit(Return const& _return) override;
virtual bool visit(MemberAccess const& _memberAccess) override;
virtual bool visit(InlineAssembly const& _inlineAssembly) override;
ErrorList& m_errors;
ErrorReporter& m_errorReporter;
/// Flag that indicates whether the current contract definition is a library.
bool m_library = false;

View File

@ -19,6 +19,7 @@
#include <memory>
#include <libsolidity/ast/AST.h>
#include <libsolidity/analysis/SemVerHandler.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <libsolidity/interface/Version.h>
using namespace std;
@ -29,27 +30,7 @@ using namespace dev::solidity;
bool SyntaxChecker::checkSyntax(ASTNode const& _astRoot)
{
_astRoot.accept(*this);
return Error::containsOnlyWarnings(m_errors);
}
void SyntaxChecker::warning(SourceLocation const& _location, string const& _description)
{
auto err = make_shared<Error>(Error::Type::Warning);
*err <<
errinfo_sourceLocation(_location) <<
errinfo_comment(_description);
m_errors.push_back(err);
}
void SyntaxChecker::syntaxError(SourceLocation const& _location, std::string const& _description)
{
auto err = make_shared<Error>(Error::Type::SyntaxError);
*err <<
errinfo_sourceLocation(_location) <<
errinfo_comment(_description);
m_errors.push_back(err);
return Error::containsOnlyWarnings(m_errorReporter.errors());
}
bool SyntaxChecker::visit(SourceUnit const&)
@ -74,11 +55,7 @@ void SyntaxChecker::endVisit(SourceUnit const& _sourceUnit)
to_string(recommendedVersion.patch());
string(";\"");
auto err = make_shared<Error>(Error::Type::Warning);
*err <<
errinfo_sourceLocation(_sourceUnit.location()) <<
errinfo_comment(errorString);
m_errors.push_back(err);
m_errorReporter.warning(_sourceUnit.location(), errorString);
}
}
@ -87,7 +64,7 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma)
solAssert(!_pragma.tokens().empty(), "");
solAssert(_pragma.tokens().size() == _pragma.literals().size(), "");
if (_pragma.tokens()[0] != Token::Identifier || _pragma.literals()[0] != "solidity")
syntaxError(_pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\"");
m_errorReporter.syntaxError(_pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\"");
else
{
vector<Token::Value> tokens(_pragma.tokens().begin() + 1, _pragma.tokens().end());
@ -96,7 +73,7 @@ bool SyntaxChecker::visit(PragmaDirective const& _pragma)
auto matchExpression = parser.parse();
SemVerVersion currentVersion{string(VersionString)};
if (!matchExpression.matches(currentVersion))
syntaxError(
m_errorReporter.syntaxError(
_pragma.location(),
"Source file requires different compiler version (current compiler is " +
string(VersionString) + " - note that nightly builds are considered to be "
@ -116,7 +93,7 @@ bool SyntaxChecker::visit(ModifierDefinition const&)
void SyntaxChecker::endVisit(ModifierDefinition const& _modifier)
{
if (!m_placeholderFound)
syntaxError(_modifier.body().location(), "Modifier body does not contain '_'.");
m_errorReporter.syntaxError(_modifier.body().location(), "Modifier body does not contain '_'.");
m_placeholderFound = false;
}
@ -146,7 +123,7 @@ bool SyntaxChecker::visit(Continue const& _continueStatement)
{
if (m_inLoopDepth <= 0)
// we're not in a for/while loop, report syntax error
syntaxError(_continueStatement.location(), "\"continue\" has to be in a \"for\" or \"while\" loop.");
m_errorReporter.syntaxError(_continueStatement.location(), "\"continue\" has to be in a \"for\" or \"while\" loop.");
return true;
}
@ -154,14 +131,14 @@ bool SyntaxChecker::visit(Break const& _breakStatement)
{
if (m_inLoopDepth <= 0)
// we're not in a for/while loop, report syntax error
syntaxError(_breakStatement.location(), "\"break\" has to be in a \"for\" or \"while\" loop.");
m_errorReporter.syntaxError(_breakStatement.location(), "\"break\" has to be in a \"for\" or \"while\" loop.");
return true;
}
bool SyntaxChecker::visit(UnaryOperation const& _operation)
{
if (_operation.getOperator() == Token::Add)
warning(_operation.location(), "Use of unary + is deprecated.");
m_errorReporter.warning(_operation.location(), "Use of unary + is deprecated.");
return true;
}
@ -171,3 +148,15 @@ bool SyntaxChecker::visit(PlaceholderStatement const&)
return true;
}
bool SyntaxChecker::visit(FunctionTypeName const& _node)
{
for (auto const& decl: _node.parameterTypeList()->parameters())
if (!decl->name().empty())
m_errorReporter.warning(decl->location(), "Naming function type parameters is deprecated.");
for (auto const& decl: _node.returnParameterTypeList()->parameters())
if (!decl->name().empty())
m_errorReporter.warning(decl->location(), "Naming function type return parameters is deprecated.");
return true;
}

View File

@ -38,14 +38,11 @@ class SyntaxChecker: private ASTConstVisitor
{
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) {}
SyntaxChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
bool checkSyntax(ASTNode const& _astRoot);
private:
/// Adds a new error to the list of errors.
void warning(SourceLocation const& _location, std::string const& _description);
void syntaxError(SourceLocation const& _location, std::string const& _description);
virtual bool visit(SourceUnit const& _sourceUnit) override;
virtual void endVisit(SourceUnit const& _sourceUnit) override;
@ -66,7 +63,9 @@ private:
virtual bool visit(PlaceholderStatement const& _placeholderStatement) override;
ErrorList& m_errors;
virtual bool visit(FunctionTypeName const& _node) override;
ErrorReporter& m_errorReporter;
/// Flag that indicates whether a function modifier actually contains '_'.
bool m_placeholderFound = false;

File diff suppressed because it is too large Load Diff

View File

@ -22,7 +22,6 @@
#pragma once
#include <libsolidity/analysis/TypeChecker.h>
#include <libsolidity/ast/Types.h>
#include <libsolidity/ast/ASTAnnotations.h>
#include <libsolidity/ast/ASTForward.h>
@ -33,6 +32,7 @@ namespace dev
namespace solidity
{
class ErrorReporter;
/**
* The module that performs type analysis on the AST, checks the applicability of operations on
@ -43,7 +43,7 @@ class TypeChecker: private ASTConstVisitor
{
public:
/// @param _errors the reference to the list of errors and warnings to add them found during type checking.
TypeChecker(ErrorList& _errors): m_errors(_errors) {}
TypeChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
/// 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
@ -56,14 +56,6 @@ public:
TypePointer const& type(VariableDeclaration const& _variable) const;
private:
/// Adds a new error to the list of errors.
void typeError(SourceLocation const& _location, std::string const& _description);
/// Adds a new warning to the list of errors.
void warning(SourceLocation const& _location, std::string const& _description);
/// Adds a new error to the list of errors and throws to abort type checking.
void fatalTypeError(SourceLocation const& _location, std::string const& _description);
virtual bool visit(ContractDefinition const& _contract) override;
/// Checks that two functions defined in this contract with the same name have different
@ -77,6 +69,9 @@ private:
void checkContractExternalTypeClashes(ContractDefinition const& _contract);
/// Checks that all requirements for a library are fulfilled if this is a library.
void checkLibraryRequirements(ContractDefinition const& _contract);
/// Checks (and warns) if a tuple assignment might cause unexpected overwrites in storage.
/// Should only be called if the left hand side is tuple-typed.
void checkDoubleStorageAssignment(Assignment const& _assignment);
virtual void endVisit(InheritanceSpecifier const& _inheritance) override;
virtual void endVisit(UsingForDirective const& _usingFor) override;
@ -127,7 +122,7 @@ private:
ContractDefinition const* m_scope = nullptr;
ErrorList& m_errors;
ErrorReporter& m_errorReporter;
};
}

View File

@ -20,10 +20,8 @@
* Solidity abstract syntax tree.
*/
#include <libsolidity/interface/Utils.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/ast/AST_accept.h>
#include <libdevcore/SHA3.h>
@ -532,18 +530,26 @@ IdentifierAnnotation& Identifier::annotation() const
return dynamic_cast<IdentifierAnnotation&>(*m_annotation);
}
bool Literal::isHexNumber() const
{
if (token() != Token::Number)
return false;
return boost::starts_with(value(), "0x");
}
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;
if (!isHexNumber())
return false;
return abs(int(value().length()) - 42) <= 1;
}
bool Literal::passesAddressChecksum() const
{
string lit = value();
solAssert(lit.substr(0, 2) == "0x", "Expected hex prefix");
return dev::passesAddressChecksum(lit, true);
solAssert(isHexNumber(), "Expected hex number");
return dev::passesAddressChecksum(value(), true);
}

View File

@ -29,7 +29,6 @@
#include <boost/noncopyable.hpp>
#include <libevmasm/SourceLocation.h>
#include <libevmasm/Instruction.h>
#include <libsolidity/interface/Utils.h>
#include <libsolidity/ast/ASTForward.h>
#include <libsolidity/parsing/Token.h>
#include <libsolidity/ast/Types.h>
@ -583,8 +582,7 @@ public:
bool isPayable() const { return m_isPayable; }
std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; }
std::vector<ASTPointer<VariableDeclaration>> const& returnParameters() const { return m_returnParameters->parameters(); }
Block const& body() const { return *m_body; }
Block const& body() const { solAssert(m_body, ""); return *m_body; }
virtual bool isVisibleInContract() const override
{
return Declaration::isVisibleInContract() && !isConstructor() && !name().empty();
@ -874,6 +872,8 @@ public:
std::vector<ASTPointer<VariableDeclaration>> const& parameterTypes() const { return m_parameterTypes->parameters(); }
std::vector<ASTPointer<VariableDeclaration>> const& returnParameterTypes() const { return m_returnTypes->parameters(); }
ASTPointer<ParameterList> const& parameterTypeList() const { return m_parameterTypes; }
ASTPointer<ParameterList> const& returnParameterTypeList() const { return m_returnTypes; }
Declaration::Visibility visibility() const
{
@ -1314,7 +1314,7 @@ private:
/**
* Tuple, parenthesized expression, or bracketed expression.
* Examples: (1, 2), (x,), (x), (), [1, 2],
* Examples: (1, 2), (x,), (x), (), [1, 2],
* Individual components might be empty shared pointers (as in the second example).
* The respective types in lvalue context are: 2-tuple, 2-tuple (with wildcard), type of x, 0-tuple
* Not in lvalue context: 2-tuple, _1_-tuple, type of x, 0-tuple.
@ -1327,8 +1327,8 @@ public:
std::vector<ASTPointer<Expression>> const& _components,
bool _isArray
):
Expression(_location),
m_components(_components),
Expression(_location),
m_components(_components),
m_isArray(_isArray) {}
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
@ -1590,6 +1590,9 @@ public:
SubDenomination subDenomination() const { return m_subDenomination; }
/// @returns true if this is a number with a hex prefix.
bool isHexNumber() const;
/// @returns true if this looks like a checksummed address.
bool looksLikeAddress() const;
/// @returns true if it passes the address checksum test.

View File

@ -200,12 +200,17 @@ struct BinaryOperationAnnotation: ExpressionAnnotation
TypePointer commonType;
};
enum class FunctionCallKind
{
Unset,
FunctionCall,
TypeConversion,
StructConstructorCall
};
struct FunctionCallAnnotation: ExpressionAnnotation
{
/// Whether this is an explicit type conversion.
bool isTypeConversion = false;
/// Whether this is a struct constructor call.
bool isStructConstructorCall = false;
FunctionCallKind kind = FunctionCallKind::Unset;
};
}

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,6 @@
#include <stack>
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/interface/Utils.h>
#include <libsolidity/ast/ASTAnnotations.h>
#include <json/json.h>
@ -42,15 +41,23 @@ class ASTJsonConverter: public ASTConstVisitor
{
public:
/// Create a converter to JSON for the given abstract syntax tree.
/// @a _legacy if true, use legacy format
/// @a _sourceIndices is used to abbreviate source names in source locations.
explicit ASTJsonConverter(
ASTNode const& _ast,
bool _legacy,
std::map<std::string, unsigned> _sourceIndices = std::map<std::string, unsigned>()
);
/// Output the json representation of the AST to _stream.
void print(std::ostream& _stream);
Json::Value const& json();
void print(std::ostream& _stream, ASTNode const& _node);
Json::Value toJson(ASTNode const& _node);
template <class T>
Json::Value toJson(std::vector<ASTPointer<T>> const& _nodes)
{
Json::Value ret(Json::arrayValue);
for (auto const& n: _nodes)
ret.append(n ? toJson(*n) : Json::nullValue);
return ret;
}
bool visit(SourceUnit const& _node) override;
bool visit(PragmaDirective const& _node) override;
bool visit(ImportDirective const& _node) override;
@ -97,82 +104,61 @@ public:
bool visit(ElementaryTypeNameExpression const& _node) override;
bool visit(Literal const& _node) override;
void endVisit(SourceUnit const&) override;
void endVisit(PragmaDirective const&) override;
void endVisit(ImportDirective const&) override;
void endVisit(ContractDefinition const&) override;
void endVisit(InheritanceSpecifier const&) override;
void endVisit(UsingForDirective const&) override;
void endVisit(StructDefinition const&) override;
void endVisit(EnumDefinition const&) override;
void endVisit(EnumValue const&) override;
void endVisit(ParameterList const&) override;
void endVisit(FunctionDefinition const&) override;
void endVisit(VariableDeclaration const&) override;
void endVisit(ModifierDefinition const&) override;
void endVisit(ModifierInvocation const&) override;
void endVisit(EventDefinition const&) override;
void endVisit(TypeName const&) override;
void endVisit(ElementaryTypeName const&) override;
void endVisit(UserDefinedTypeName const&) override;
void endVisit(FunctionTypeName const&) override;
void endVisit(Mapping const&) override;
void endVisit(ArrayTypeName const&) override;
void endVisit(InlineAssembly const&) override;
void endVisit(Block const&) override;
void endVisit(PlaceholderStatement const&) override;
void endVisit(IfStatement const&) override;
void endVisit(WhileStatement const&) override;
void endVisit(ForStatement const&) override;
void endVisit(Continue const&) override;
void endVisit(Break const&) override;
void endVisit(Return const&) override;
void endVisit(Throw const&) override;
void endVisit(VariableDeclarationStatement const&) override;
void endVisit(ExpressionStatement const&) override;
void endVisit(Conditional const&) override;
void endVisit(Assignment const&) override;
void endVisit(TupleExpression const&) override;
void endVisit(UnaryOperation const&) override;
void endVisit(BinaryOperation const&) override;
void endVisit(FunctionCall const&) override;
void endVisit(NewExpression const&) override;
void endVisit(MemberAccess const&) override;
void endVisit(IndexAccess const&) override;
void endVisit(Identifier const&) override;
void endVisit(ElementaryTypeNameExpression const&) override;
void endVisit(Literal const&) override;
private:
void process();
void addJsonNode(
void setJsonNode(
ASTNode const& _node,
std::string const& _nodeName,
std::initializer_list<std::pair<std::string const, Json::Value const>> _attributes,
bool _hasChildren
std::initializer_list<std::pair<std::string, Json::Value>>&& _attributes
);
void addJsonNode(
void setJsonNode(
ASTNode const& _node,
std::string const& _nodeName,
std::vector<std::pair<std::string const, Json::Value const>> const& _attributes,
bool _hasChildren
std::vector<std::pair<std::string, Json::Value>>&& _attributes
);
std::string sourceLocationToString(SourceLocation const& _location) const;
std::string namePathToString(std::vector<ASTString> const& _namePath) const;
Json::Value idOrNull(ASTNode const* _pt)
{
return _pt ? Json::Value(nodeId(*_pt)) : Json::nullValue;
}
Json::Value toJsonOrNull(ASTNode const* _node)
{
return _node ? toJson(*_node) : Json::nullValue;
}
Json::Value inlineAssemblyIdentifierToJson(std::pair<assembly::Identifier const* , InlineAssemblyAnnotation::ExternalIdentifierInfo> _info);
std::string visibility(Declaration::Visibility const& _visibility);
std::string location(VariableDeclaration::Location _location);
std::string contractKind(ContractDefinition::ContractKind _kind);
std::string functionCallKind(FunctionCallKind _kind);
std::string literalTokenKind(Token::Value _token);
std::string type(Expression const& _expression);
std::string type(VariableDeclaration const& _varDecl);
inline void goUp()
int nodeId(ASTNode const& _node)
{
solAssert(!m_jsonNodePtrs.empty(), "Uneven json nodes stack. Internal error.");
m_jsonNodePtrs.pop();
return _node.id();
}
template<class Container>
Json::Value getContainerIds(Container const& container)
{
Json::Value tmp(Json::arrayValue);
for (auto const& element: container)
{
solAssert(element, "");
tmp.append(nodeId(*element));
}
return tmp;
}
Json::Value typePointerToJson(TypePointer _tp);
Json::Value typePointerToJson(std::shared_ptr<std::vector<TypePointer>> _tps);
void appendExpressionAttributes(
std::vector<std::pair<std::string, Json::Value>> &_attributes,
ExpressionAnnotation const& _annotation
);
bool m_legacy = false; ///< if true, use legacy format
bool m_inEvent = false; ///< whether we are currently inside an event or not
bool processed = false;
Json::Value m_astJson;
std::stack<Json::Value*> m_jsonNodePtrs;
ASTNode const* m_ast;
Json::Value m_currentValue;
std::map<std::string, unsigned> m_sourceIndices;
};

View File

@ -22,7 +22,6 @@
#include <libsolidity/ast/Types.h>
#include <libsolidity/interface/Utils.h>
#include <libsolidity/ast/AST.h>
#include <libdevcore/CommonIO.h>
@ -2183,6 +2182,8 @@ string FunctionType::identifier() const
case Kind::ArrayPush: id += "arraypush"; break;
case Kind::ByteArrayPush: id += "bytearraypush"; break;
case Kind::ObjectCreation: id += "objectcreation"; break;
case Kind::Assert: id += "assert"; break;
case Kind::Require: id += "require";break;
default: solAssert(false, "Unknown function location."); break;
}
if (isConstant())
@ -2247,6 +2248,16 @@ TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const
return TypePointer();
}
TypePointer FunctionType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const
{
if (_other->category() != category() || !(_operator == Token::Equal || _operator == Token::NotEqual))
return TypePointer();
FunctionType const& other = dynamic_cast<FunctionType const&>(*_other);
if (kind() == Kind::Internal && other.kind() == Kind::Internal && sizeOnStack() == 1 && other.sizeOnStack() == 1)
return commonType(shared_from_this(), _other);
return TypePointer();
}
string FunctionType::canonicalName(bool) const
{
solAssert(m_kind == Kind::External, "");

View File

@ -933,6 +933,7 @@ public:
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 TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override;
virtual std::string canonicalName(bool /*_addDataLocation*/) const override;
virtual std::string toString(bool _short) const override;
virtual unsigned calldataEncodedSize(bool _padded) const override;
@ -1038,6 +1039,7 @@ public:
virtual std::string toString(bool _short) const override;
virtual std::string canonicalName(bool _addDataLocation) const override;
virtual bool canLiveOutsideStorage() const override { return false; }
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
virtual TypePointer encodingType() const override
{
return std::make_shared<IntegerType>(256);
@ -1116,11 +1118,7 @@ public:
explicit ModuleType(SourceUnit const& _source): m_sourceUnit(_source) {}
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override
{
return TypePointer();
}
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; }
@ -1178,6 +1176,7 @@ public:
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 TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
virtual unsigned calldataEncodedSize(bool _padded) const override { (void)_padded; return 32; }
virtual bool canBeStored() const override { return false; }
virtual bool canLiveOutsideStorage() const override { return false; }

View File

@ -25,7 +25,7 @@
#include <libsolidity/codegen/CompilerContext.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/ast/Types.h>
#include <libsolidity/interface/Utils.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/codegen/LValue.h>
using namespace std;
@ -449,7 +449,7 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
m_context << Instruction::DUP3 << Instruction::ADD << Instruction::SWAP2;
if (_sourceType.isDynamicallySized())
{
// actual array data is stored at SHA3(storage_offset)
// actual array data is stored at KECCAK256(storage_offset)
m_context << Instruction::SWAP1;
utils.computeHashStatic();
m_context << Instruction::SWAP1;
@ -731,7 +731,7 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const
_context << Instruction::POP;
}
// Change of length for a regular array (i.e. length at location, data at sha3(location)).
// Change of length for a regular array (i.e. length at location, data at KECCAK256(location)).
// stack: ref new_length old_length
// store new length
_context << Instruction::DUP2;

View File

@ -25,8 +25,12 @@
#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 <libsolidity/interface/ErrorReporter.h>
#include <libsolidity/parsing/Scanner.h>
#include <libsolidity/inlineasm/AsmParser.h>
#include <libsolidity/inlineasm/AsmCodeGen.h>
#include <libsolidity/inlineasm/AsmAnalysis.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <boost/algorithm/string/replace.hpp>
@ -120,6 +124,7 @@ void CompilerContext::addVariable(VariableDeclaration const& _declaration,
unsigned _offsetToCurrent)
{
solAssert(m_asm->deposit() >= 0 && unsigned(m_asm->deposit()) >= _offsetToCurrent, "");
solAssert(m_localVariables.count(&_declaration) == 0, "Variable already present");
m_localVariables[&_declaration] = unsigned(m_asm->deposit()) - _offsetToCurrent;
}
@ -240,6 +245,20 @@ CompilerContext& CompilerContext::appendConditionalInvalid()
return *this;
}
CompilerContext& CompilerContext::appendRevert()
{
return *this << u256(0) << u256(0) << Instruction::REVERT;
}
CompilerContext& CompilerContext::appendConditionalRevert()
{
*this << Instruction::ISZERO;
eth::AssemblyItem afterTag = appendConditionalJump();
appendRevert();
*this << afterTag;
return *this;
}
void CompilerContext::resetVisitedNodes(ASTNode const* _node)
{
stack<ASTNode const*> newStack;
@ -264,12 +283,13 @@ void CompilerContext::appendInlineAssembly(
assembly = &replacedAssembly;
}
unsigned startStackHeight = stackHeight();
int startStackHeight = stackHeight();
assembly::ExternalIdentifierAccess identifierAccess;
julia::ExternalIdentifierAccess identifierAccess;
identifierAccess.resolve = [&](
assembly::Identifier const& _identifier,
assembly::IdentifierContext
julia::IdentifierContext,
bool
)
{
auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name);
@ -277,31 +297,42 @@ void CompilerContext::appendInlineAssembly(
};
identifierAccess.generateCode = [&](
assembly::Identifier const& _identifier,
assembly::IdentifierContext _context,
eth::Assembly& _assembly
julia::IdentifierContext _context,
julia::AbstractAssembly& _assembly
)
{
auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name);
solAssert(it != _localVariables.end(), "");
unsigned stackDepth = _localVariables.end() - it;
int stackDiff = _assembly.deposit() - startStackHeight + stackDepth;
if (_context == assembly::IdentifierContext::LValue)
int stackDepth = _localVariables.end() - it;
int stackDiff = _assembly.stackHeight() - startStackHeight + stackDepth;
if (_context == julia::IdentifierContext::LValue)
stackDiff -= 1;
if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
errinfo_comment("Stack too deep, try removing local variables.")
errinfo_comment("Stack too deep (" + to_string(stackDiff) + "), try removing local variables.")
);
if (_context == assembly::IdentifierContext::RValue)
_assembly.append(dupInstruction(stackDiff));
if (_context == julia::IdentifierContext::RValue)
_assembly.appendInstruction(dupInstruction(stackDiff));
else
{
_assembly.append(swapInstruction(stackDiff));
_assembly.append(Instruction::POP);
_assembly.appendInstruction(swapInstruction(stackDiff));
_assembly.appendInstruction(Instruction::POP);
}
};
solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), "Failed to assemble inline assembly block.");
ErrorList errors;
ErrorReporter errorReporter(errors);
auto scanner = make_shared<Scanner>(CharStream(*assembly), "--CODEGEN--");
auto parserResult = assembly::Parser(errorReporter).parse(scanner);
solAssert(parserResult, "Failed to parse inline assembly block.");
solAssert(errorReporter.errors().empty(), "Failed to parse inline assembly block.");
assembly::AsmAnalysisInfo analysisInfo;
assembly::AsmAnalyzer analyzer(analysisInfo, errorReporter, false, identifierAccess.resolve);
solAssert(analyzer.analyze(*parserResult), "Failed to analyze inline assembly block.");
solAssert(errorReporter.errors().empty(), "Failed to analyze inline assembly block.");
assembly::CodeGenerator::assemble(*parserResult, analysisInfo, *m_asm, identifierAccess);
}
FunctionDefinition const& CompilerContext::resolveVirtualFunction(

View File

@ -136,13 +136,15 @@ public:
/// Appends a JUMP to a new tag and @returns the tag
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);
CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary);
/// Appends an INVALID instruction
CompilerContext& appendInvalid();
CompilerContext& appendInvalid();
/// Appends a conditional INVALID instruction
CompilerContext& appendConditionalInvalid();
/// Returns an "ErrorTag"
eth::AssemblyItem errorTag() { return m_asm->errorTag(); }
CompilerContext& appendConditionalInvalid();
/// Appends a REVERT(0, 0) call
CompilerContext& appendRevert();
/// Appends a conditional REVERT(0, 0) call
CompilerContext& appendConditionalRevert();
/// Appends a JUMP to a specific tag
CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJump(_tag); return *this; }
/// Appends pushing of a new tag and @returns the new tag.
@ -151,10 +153,10 @@ public:
eth::AssemblyItem newTag() { return m_asm->newTag(); }
/// Adds a subroutine to the code (in the data section) and pushes its size (via a tag)
/// on the stack. @returns the pushsub assembly item.
eth::AssemblyItem addSubroutine(eth::AssemblyPointer const& _assembly) { auto sub = m_asm->newSub(_assembly); m_asm->append(m_asm->newPushSubSize(size_t(sub.data()))); return sub; }
void pushSubroutineSize(size_t _subRoutine) { m_asm->append(m_asm->newPushSubSize(_subRoutine)); }
eth::AssemblyItem addSubroutine(eth::AssemblyPointer const& _assembly) { return m_asm->appendSubroutine(_assembly); }
void pushSubroutineSize(size_t _subRoutine) { m_asm->pushSubroutineSize(_subRoutine); }
/// Pushes the offset of the subroutine.
void pushSubroutineOffset(size_t _subRoutine) { m_asm->append(eth::AssemblyItem(eth::PushSub, _subRoutine)); }
void pushSubroutineOffset(size_t _subRoutine) { m_asm->pushSubroutineOffset(_subRoutine); }
/// Pushes the size of the final program
void appendProgramSize() { m_asm->appendProgramSize(); }
/// Adds data to the data section, pushes a reference to the stack

View File

@ -128,7 +128,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
m_context << Instruction::DUP1;
storeStringData(bytesConstRef(str->value()));
if (_padToWordBoundaries)
m_context << u256(((str->value().size() + 31) / 32) * 32);
m_context << u256(max<size_t>(32, ((str->value().size() + 31) / 32) * 32));
else
m_context << u256(str->value().size());
m_context << Instruction::ADD;
@ -180,6 +180,9 @@ void CompilerUtils::encodeToMemory(
t = t->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType();
}
if (_givenTypes.empty())
return;
// Stack during operation:
// <v1> <v2> ... <vn> <mem_start> <dyn_head_1> ... <dyn_head_r> <end_of_mem>
// The values dyn_head_i are added during the first loop and they point to the head part
@ -299,40 +302,15 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
m_context << Instruction::SWAP1 << Instruction::POP;
}
void CompilerUtils::memoryCopyPrecompile()
{
// Stack here: size target source
m_context.appendInlineAssembly(R"(
{
let words := div(add(len, 31), 32)
let cost := add(15, mul(3, words))
jumpi(invalidJumpLabel, iszero(call(cost, $identityContractAddress, 0, src, len, dst, len)))
}
)",
{ "len", "dst", "src" },
map<string, string> {
{ "$identityContractAddress", toString(identityContractAddress) }
}
);
m_context << Instruction::POP << Instruction::POP << Instruction::POP;
}
void CompilerUtils::memoryCopy32()
{
// Stack here: size target source
m_context.appendInlineAssembly(R"(
{
jumpi(end, eq(len, 0))
start:
mstore(dst, mload(src))
jumpi(end, iszero(gt(len, 32)))
dst := add(dst, 32)
src := add(src, 32)
len := sub(len, 32)
jump(start)
end:
for { let i := 0 } lt(i, len) { i := add(i, 32) } {
mstore(add(dst, i), mload(add(src, i)))
}
}
)",
{ "len", "dst", "src" }
@ -346,21 +324,22 @@ void CompilerUtils::memoryCopy()
m_context.appendInlineAssembly(R"(
{
// copy 32 bytes at once
start32:
jumpi(end32, lt(len, 32))
mstore(dst, mload(src))
dst := add(dst, 32)
src := add(src, 32)
len := sub(len, 32)
jump(start32)
end32:
// copy 32 bytes at once
for
{}
iszero(lt(len, 32))
{
dst := add(dst, 32)
src := add(src, 32)
len := sub(len, 32)
}
{ mstore(dst, mload(src)) }
// copy the remainder (0 < len < 32)
let mask := sub(exp(256, sub(32, len)), 1)
let srcpart := and(mload(src), not(mask))
let dstpart := and(mload(dst), mask)
mstore(dst, or(srcpart, dstpart))
// copy the remainder (0 < len < 32)
let mask := sub(exp(256, sub(32, len)), 1)
let srcpart := and(mload(src), not(mask))
let dstpart := and(mload(dst), mask)
mstore(dst, or(srcpart, dstpart))
}
)",
{ "len", "dst", "src" }
@ -374,13 +353,16 @@ void CompilerUtils::splitExternalFunctionType(bool _leftAligned)
// address (right aligned), function identifier (right aligned)
if (_leftAligned)
{
m_context << Instruction::DUP1 << (u256(1) << (64 + 32)) << Instruction::SWAP1 << Instruction::DIV;
m_context << Instruction::DUP1;
rightShiftNumberOnStack(64 + 32, false);
// <input> <address>
m_context << Instruction::SWAP1 << (u256(1) << 64) << Instruction::SWAP1 << Instruction::DIV;
m_context << Instruction::SWAP1;
rightShiftNumberOnStack(64, false);
}
else
{
m_context << Instruction::DUP1 << (u256(1) << 32) << Instruction::SWAP1 << Instruction::DIV;
m_context << Instruction::DUP1;
rightShiftNumberOnStack(32, false);
m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1;
}
m_context << u256(0xffffffffUL) << Instruction::AND;
@ -392,10 +374,10 @@ void CompilerUtils::combineExternalFunctionType(bool _leftAligned)
m_context << u256(0xffffffffUL) << Instruction::AND << Instruction::SWAP1;
if (!_leftAligned)
m_context << ((u256(1) << 160) - 1) << Instruction::AND;
m_context << (u256(1) << 32) << Instruction::MUL;
leftShiftNumberOnStack(32);
m_context << Instruction::OR;
if (_leftAligned)
m_context << (u256(1) << 64) << Instruction::MUL;
leftShiftNumberOnStack(64);
}
void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function)
@ -404,14 +386,21 @@ void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function)
// If there is a runtime context, we have to merge both labels into the same
// stack slot in case we store it in storage.
if (CompilerContext* rtc = m_context.runtimeContext())
{
leftShiftNumberOnStack(32);
m_context <<
(u256(1) << 32) <<
Instruction::MUL <<
rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) <<
Instruction::OR;
}
}
void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded, bool _chopSignBits)
void CompilerUtils::convertType(
Type const& _typeOnStack,
Type const& _targetType,
bool _cleanupNeeded,
bool _chopSignBits,
bool _asPartOfArgumentDecoding
)
{
// For a type extension, we need to remove all higher-order bits that we might have ignored in
// previous operations.
@ -440,7 +429,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
// conversion from bytes to integer. no need to clean the high bit
// only to shift right because of opposite alignment
IntegerType const& targetIntegerType = dynamic_cast<IntegerType const&>(_targetType);
m_context << (u256(1) << (256 - typeOnStack.numBytes() * 8)) << Instruction::SWAP1 << Instruction::DIV;
rightShiftNumberOnStack(256 - typeOnStack.numBytes() * 8, false);
if (targetIntegerType.numBits() < typeOnStack.numBytes() * 8)
convertType(IntegerType(typeOnStack.numBytes() * 8), _targetType, _cleanupNeeded);
}
@ -469,7 +458,10 @@ 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.appendConditionalInvalid();
if (_asPartOfArgumentDecoding)
m_context.appendConditionalRevert();
else
m_context.appendConditionalInvalid();
enumOverflowCheckPending = false;
}
break;
@ -488,7 +480,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
if (auto typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack))
if (targetBytesType.numBytes() * 8 > typeOnStack->numBits())
cleanHigherOrderBits(*typeOnStack);
m_context << (u256(1) << (256 - targetBytesType.numBytes() * 8)) << Instruction::MUL;
leftShiftNumberOnStack(256 - targetBytesType.numBytes() * 8);
}
else if (targetTypeCategory == Type::Category::Enum)
{
@ -953,7 +945,7 @@ unsigned CompilerUtils::sizeOnStack(vector<shared_ptr<Type const>> const& _varia
void CompilerUtils::computeHashStatic()
{
storeInMemory(0);
m_context << u256(32) << u256(0) << Instruction::SHA3;
m_context << u256(32) << u256(0) << Instruction::KECCAK256;
}
void CompilerUtils::storeStringData(bytesConstRef _data)
@ -998,13 +990,13 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda
{
bool leftAligned = _type.category() == Type::Category::FixedBytes;
// add leading or trailing zeros by dividing/multiplying depending on alignment
u256 shiftFactor = u256(1) << ((32 - numBytes) * 8);
m_context << shiftFactor << Instruction::SWAP1 << Instruction::DIV;
int shiftFactor = (32 - numBytes) * 8;
rightShiftNumberOnStack(shiftFactor, false);
if (leftAligned)
m_context << shiftFactor << Instruction::MUL;
leftShiftNumberOnStack(shiftFactor);
}
if (_fromCalldata)
convertType(_type, _type, true);
convertType(_type, _type, true, false, true);
return numBytes;
}
@ -1019,6 +1011,18 @@ void CompilerUtils::cleanHigherOrderBits(IntegerType const& _typeOnStack)
m_context << ((u256(1) << _typeOnStack.numBits()) - 1) << Instruction::AND;
}
void CompilerUtils::leftShiftNumberOnStack(unsigned _bits)
{
solAssert(_bits < 256, "");
m_context << (u256(1) << _bits) << Instruction::MUL;
}
void CompilerUtils::rightShiftNumberOnStack(unsigned _bits, bool _isSigned)
{
solAssert(_bits < 256, "");
m_context << (u256(1) << _bits) << Instruction::SWAP1 << (_isSigned ? Instruction::SDIV : Instruction::DIV);
}
unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords)
{
unsigned numBytes = _type.calldataEncodedSize(_padToWords);
@ -1031,7 +1035,7 @@ unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords)
convertType(_type, _type, true);
if (numBytes != 32 && !leftAligned && !_padToWords)
// shift the value accordingly before storing
m_context << (u256(1) << ((32 - numBytes) * 8)) << Instruction::MUL;
leftShiftNumberOnStack((32 - numBytes) * 8);
}
return numBytes;
}

View File

@ -109,15 +109,13 @@ public:
/// Stack post: <updated_memptr>
void zeroInitialiseMemoryArray(ArrayType const& _type);
/// Uses a CALL to the identity contract to perform a memory-to-memory copy.
/// Stack pre: <size> <target> <source>
/// Stack post:
void memoryCopyPrecompile();
/// Copies full 32 byte words in memory (regions cannot overlap), i.e. may copy more than length.
/// Length can be zero, in this case, it copies nothing.
/// Stack pre: <size> <target> <source>
/// Stack post:
void memoryCopy32();
/// Copies data in memory (regions cannot overlap).
/// Length can be zero, in this case, it copies nothing.
/// Stack pre: <size> <target> <source>
/// Stack post:
void memoryCopy();
@ -139,7 +137,15 @@ public:
/// If @a _cleanupNeeded, high order bits cleanup is also done if no type conversion would be
/// necessary.
/// If @a _chopSignBits, the function resets the signed bits out of the width of the signed integer.
void convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded = false, bool _chopSignBits = false);
/// If @a _asPartOfArgumentDecoding is true, failed conversions are flagged via REVERT,
/// otherwise they are flagged with INVALID.
void convertType(
Type const& _typeOnStack,
Type const& _targetType,
bool _cleanupNeeded = false,
bool _chopSignBits = false,
bool _asPartOfArgumentDecoding = false
);
/// Creates a zero-value for the given type and puts it onto the stack. This might allocate
/// memory for memory references.
@ -170,7 +176,13 @@ public:
static unsigned sizeOnStack(std::vector<T> const& _variables);
static unsigned sizeOnStack(std::vector<std::shared_ptr<Type const>> const& _variableTypes);
/// Appends code that computes tha SHA3 hash of the topmost stack element of 32 byte type.
/// Helper function to shift top value on the stack to the left.
void leftShiftNumberOnStack(unsigned _bits);
/// Helper function to shift top value on the stack to the right.
void rightShiftNumberOnStack(unsigned _bits, bool _isSigned = false);
/// Appends code that computes tha Keccak-256 hash of the topmost stack element of 32 byte type.
void computeHashStatic();
/// Bytes we need to the start of call data.

View File

@ -21,15 +21,20 @@
*/
#include <libsolidity/codegen/ContractCompiler.h>
#include <algorithm>
#include <boost/range/adaptor/reversed.hpp>
#include <libsolidity/inlineasm/AsmCodeGen.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <libsolidity/codegen/ExpressionCompiler.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <libevmasm/Instruction.h>
#include <libevmasm/Assembly.h>
#include <libevmasm/GasMeter.h>
#include <libsolidity/inlineasm/AsmCodeGen.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/codegen/ExpressionCompiler.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <boost/range/adaptor/reversed.hpp>
#include <algorithm>
using namespace std;
using namespace dev;
using namespace dev::solidity;
@ -106,7 +111,7 @@ void ContractCompiler::appendCallValueCheck()
{
// Throw if function is not payable but call contained ether.
m_context << Instruction::CALLVALUE;
m_context.appendConditionalInvalid();
m_context.appendConditionalRevert();
}
void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract)
@ -262,16 +267,22 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
m_context << notFound;
if (fallback)
{
m_context.setStackOffset(0);
if (!fallback->isPayable())
appendCallValueCheck();
// Return tag is used to jump out of the function.
eth::AssemblyItem returnTag = m_context.pushNewTag();
fallback->accept(*this);
m_context << returnTag;
appendReturnValuePacker(FunctionType(*fallback).returnParameterTypes(), _contract.isLibrary());
solAssert(FunctionType(*fallback).parameterTypes().empty(), "");
solAssert(FunctionType(*fallback).returnParameterTypes().empty(), "");
// Return tag gets consumed.
m_context.adjustStackOffset(-1);
m_context << Instruction::STOP;
}
else
m_context.appendInvalid();
m_context.appendRevert();
for (auto const& it: interfaceFunctions)
{
@ -280,16 +291,29 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
CompilerContext::LocationSetter locationSetter(m_context, functionType->declaration());
m_context << callDataUnpackerEntryPoints.at(it.first);
m_context.setStackOffset(0);
// We have to allow this for libraries, because value of the previous
// call is still visible in the delegatecall.
if (!functionType->isPayable() && !_contract.isLibrary())
appendCallValueCheck();
// Return tag is used to jump out of the function.
eth::AssemblyItem returnTag = m_context.pushNewTag();
m_context << CompilerUtils::dataStartOffset;
appendCalldataUnpacker(functionType->parameterTypes());
if (!functionType->parameterTypes().empty())
{
// Parameter for calldataUnpacker
m_context << CompilerUtils::dataStartOffset;
appendCalldataUnpacker(functionType->parameterTypes());
}
m_context.appendJumpTo(m_context.functionEntryLabel(functionType->declaration()));
m_context << returnTag;
// Return tag and input parameters get consumed.
m_context.adjustStackOffset(
CompilerUtils(m_context).sizeOnStack(functionType->returnParameterTypes()) -
CompilerUtils(m_context).sizeOnStack(functionType->parameterTypes()) -
1
);
// Consumes the return parameters.
appendReturnValuePacker(functionType->returnParameterTypes(), _contract.isLibrary());
}
}
@ -363,7 +387,7 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter
// copy to memory
// move calldata type up again
CompilerUtils(m_context).moveIntoStack(calldataType->sizeOnStack());
CompilerUtils(m_context).convertType(*calldataType, arrayType);
CompilerUtils(m_context).convertType(*calldataType, arrayType, false, false, true);
// fetch next pointer again
CompilerUtils(m_context).moveToStackTop(arrayType.sizeOnStack());
}
@ -519,40 +543,42 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
{
ErrorList errors;
assembly::CodeGenerator codeGen(errors);
unsigned startStackHeight = m_context.stackHeight();
assembly::ExternalIdentifierAccess identifierAccess;
identifierAccess.resolve = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext)
julia::ExternalIdentifierAccess identifierAccess;
identifierAccess.resolve = [&](assembly::Identifier const& _identifier, julia::IdentifierContext, bool)
{
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
if (ref == _inlineAssembly.annotation().externalReferences.end())
return size_t(-1);
return ref->second.valueSize;
};
identifierAccess.generateCode = [&](assembly::Identifier const& _identifier, assembly::IdentifierContext _context, eth::Assembly& _assembly)
identifierAccess.generateCode = [&](assembly::Identifier const& _identifier, julia::IdentifierContext _context, julia::AbstractAssembly& _assembly)
{
auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier);
solAssert(ref != _inlineAssembly.annotation().externalReferences.end(), "");
Declaration const* decl = ref->second.declaration;
solAssert(!!decl, "");
if (_context == assembly::IdentifierContext::RValue)
if (_context == julia::IdentifierContext::RValue)
{
int const depositBefore = _assembly.deposit();
int const depositBefore = _assembly.stackHeight();
solAssert(!!decl->type(), "Type of declaration required but not yet determined.");
if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(decl))
{
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
functionDef = &m_context.resolveVirtualFunction(*functionDef);
_assembly.append(m_context.functionEntryLabel(*functionDef).pushTag());
auto functionEntryLabel = m_context.functionEntryLabel(*functionDef).pushTag();
solAssert(functionEntryLabel.data() <= std::numeric_limits<size_t>::max(), "");
_assembly.appendLabelReference(size_t(functionEntryLabel.data()));
// If there is a runtime context, we have to merge both labels into the same
// stack slot in case we store it in storage.
if (CompilerContext* rtc = m_context.runtimeContext())
{
_assembly.append(u256(1) << 32);
_assembly.append(Instruction::MUL);
_assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub()));
_assembly.append(Instruction::OR);
_assembly.appendConstant(u256(1) << 32);
_assembly.appendInstruction(Instruction::MUL);
auto runtimeEntryLabel = rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub());
solAssert(runtimeEntryLabel.data() <= std::numeric_limits<size_t>::max(), "");
_assembly.appendLabelReference(size_t(runtimeEntryLabel.data()));
_assembly.appendInstruction(Instruction::OR);
}
}
else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl))
@ -570,7 +596,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
}
else if (m_context.isLocalVariable(decl))
{
int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable);
int stackDiff = _assembly.stackHeight() - m_context.baseStackOffsetOfVariable(*variable);
if (ref->second.isSlot || ref->second.isOffset)
{
solAssert(variable->type()->dataStoredIn(DataLocation::Storage), "");
@ -587,7 +613,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
// only slot, offset is zero
if (ref->second.isOffset)
{
_assembly.append(u256(0));
_assembly.appendConstant(u256(0));
return;
}
}
@ -601,7 +627,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
errinfo_comment("Stack too deep, try removing local variables.")
);
solAssert(variable->type()->sizeOnStack() == 1, "");
_assembly.append(dupInstruction(stackDiff));
_assembly.appendInstruction(dupInstruction(stackDiff));
}
else
solAssert(false, "");
@ -610,11 +636,11 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
{
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
solAssert(contract->isLibrary(), "");
_assembly.appendLibraryAddress(contract->fullyQualifiedName());
_assembly.appendLinkerSymbol(contract->fullyQualifiedName());
}
else
solAssert(false, "Invalid declaration type.");
solAssert(_assembly.deposit() - depositBefore == int(ref->second.valueSize), "");
solAssert(_assembly.stackHeight() - depositBefore == int(ref->second.valueSize), "");
}
else
{
@ -626,25 +652,24 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
"Can only assign to stack variables in inline assembly."
);
solAssert(variable->type()->sizeOnStack() == 1, "");
int stackDiff = _assembly.deposit() - m_context.baseStackOffsetOfVariable(*variable) - 1;
int stackDiff = _assembly.stackHeight() - m_context.baseStackOffsetOfVariable(*variable) - 1;
if (stackDiff > 16 || stackDiff < 1)
BOOST_THROW_EXCEPTION(
CompilerError() <<
errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep, try removing local variables.")
errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.")
);
_assembly.append(swapInstruction(stackDiff));
_assembly.append(Instruction::POP);
_assembly.appendInstruction(swapInstruction(stackDiff));
_assembly.appendInstruction(Instruction::POP);
}
};
solAssert(_inlineAssembly.annotation().analysisInfo, "");
codeGen.assemble(
assembly::CodeGenerator::assemble(
_inlineAssembly.operations(),
*_inlineAssembly.annotation().analysisInfo,
m_context.nonConstAssembly(),
identifierAccess
);
solAssert(Error::containsOnlyWarnings(errors), "Code generation for inline assembly with errors requested.");
m_context.setStackOffset(startStackHeight);
return false;
}
@ -799,8 +824,7 @@ bool ContractCompiler::visit(Throw const& _throw)
{
CompilerContext::LocationSetter locationSetter(m_context, _throw);
// Do not send back an error detail.
m_context << u256(0) << u256(0);
m_context << Instruction::REVERT;
m_context.appendRevert();
return false;
}
@ -873,6 +897,7 @@ void ContractCompiler::appendModifierOrFunctionCode()
solAssert(m_currentFunction, "");
unsigned stackSurplus = 0;
Block const* codeBlock = nullptr;
vector<VariableDeclaration const*> addedVariables;
m_modifierDepth++;
@ -896,6 +921,7 @@ void ContractCompiler::appendModifierOrFunctionCode()
for (unsigned i = 0; i < modifier.parameters().size(); ++i)
{
m_context.addVariable(*modifier.parameters()[i]);
addedVariables.push_back(modifier.parameters()[i].get());
compileExpression(
*modifierInvocation->arguments()[i],
modifier.parameters()[i]->annotation().type
@ -922,6 +948,8 @@ void ContractCompiler::appendModifierOrFunctionCode()
m_returnTags.pop_back();
CompilerUtils(m_context).popStackSlots(stackSurplus);
for (auto var: addedVariables)
m_context.removeVariable(*var);
}
m_modifierDepth--;
}

View File

@ -32,6 +32,7 @@
#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/codegen/LValue.h>
#include <libevmasm/GasMeter.h>
using namespace std;
namespace dev
@ -87,6 +88,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
FunctionType accessorType(_varDecl);
TypePointers paramTypes = accessorType.parameterTypes();
m_context.adjustStackOffset(1 + CompilerUtils::sizeOnStack(paramTypes));
// retrieve the position of the variable
auto const& location = m_context.storageLocationOfVariable(_varDecl);
@ -110,7 +112,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
// move key to memory.
utils().copyToStackTop(paramTypes.size() - i, 1);
utils().storeInMemory(0);
m_context << u256(64) << u256(0) << Instruction::SHA3;
m_context << u256(64) << u256(0) << Instruction::KECCAK256;
// push offset
m_context << u256(0);
returnType = mappingType->valueType();
@ -434,7 +436,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{
CompilerContext::LocationSetter locationSetter(m_context, _functionCall);
if (_functionCall.annotation().isTypeConversion)
if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion)
{
solAssert(_functionCall.arguments().size() == 1, "");
solAssert(_functionCall.names().empty(), "");
@ -445,7 +447,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
}
FunctionTypePointer functionType;
if (_functionCall.annotation().isStructConstructorCall)
if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall)
{
auto const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type);
auto const& structType = dynamic_cast<StructType const&>(*type.actualType());
@ -476,7 +478,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
solAssert(found, "");
}
if (_functionCall.annotation().isStructConstructorCall)
if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall)
{
TypeType const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type);
auto const& structType = dynamic_cast<StructType const&>(*type.actualType());
@ -524,7 +526,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
if (m_context.runtimeContext())
// We have a runtime context, so we need the creation part.
m_context << (u256(1) << 32) << Instruction::SWAP1 << Instruction::DIV;
utils().rightShiftNumberOnStack(32, false);
else
// Extract the runtime part.
m_context << ((u256(1) << 32) - 1) << Instruction::AND;
@ -586,7 +588,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::CREATE;
// Check if zero (out of stack or not enough balance).
m_context << Instruction::DUP1 << Instruction::ISZERO;
m_context.appendConditionalInvalid();
m_context.appendConditionalRevert();
if (function.valueSet())
m_context << swapInstruction(1) << Instruction::POP;
break;
@ -650,7 +652,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{
// Check if zero (out of stack or not enough balance).
m_context << Instruction::ISZERO;
m_context.appendConditionalInvalid();
m_context.appendConditionalRevert();
}
break;
case FunctionType::Kind::Selfdestruct:
@ -659,9 +661,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::SELFDESTRUCT;
break;
case FunctionType::Kind::Revert:
// memory offset returned - zero length
m_context << u256(0) << u256(0);
m_context << Instruction::REVERT;
m_context.appendRevert();
break;
case FunctionType::Kind::SHA3:
{
@ -674,7 +674,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
utils().fetchFreeMemoryPointer();
utils().encodeToMemory(argumentTypes, TypePointers(), function.padArguments(), true);
utils().toSizeAfterFreeMemoryPointer();
m_context << Instruction::SHA3;
m_context << Instruction::KECCAK256;
break;
}
case FunctionType::Kind::Log0:
@ -721,7 +721,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
true
);
utils().toSizeAfterFreeMemoryPointer();
m_context << Instruction::SHA3;
m_context << Instruction::KECCAK256;
}
else
utils().convertType(
@ -887,9 +887,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
auto success = m_context.appendConditionalJump();
if (function.kind() == FunctionType::Kind::Assert)
// condition was not met, flag an error
m_context << Instruction::INVALID;
m_context.appendInvalid();
else
m_context << u256(0) << u256(0) << Instruction::REVERT;
m_context.appendRevert();
// the success branch
m_context << success;
break;
@ -1214,7 +1214,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
utils().storeInMemoryDynamic(IntegerType(256));
m_context << u256(0);
}
m_context << Instruction::SHA3;
m_context << Instruction::KECCAK256;
m_context << u256(0);
setLValueToStorageItem(_indexAccess);
}
@ -1269,7 +1269,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
m_context.appendConditionalInvalid();
m_context << Instruction::BYTE;
m_context << (u256(1) << (256 - 8)) << Instruction::MUL;
utils().leftShiftNumberOnStack(256 - 8);
}
else if (baseType.category() == Type::Category::TypeType)
{
@ -1367,6 +1367,7 @@ void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation const& _binaryO
void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type)
{
solAssert(_type.sizeOnStack() == 1, "Comparison of multi-slot types.");
if (_operator == Token::Equal || _operator == Token::NotEqual)
{
if (FunctionType const* funType = dynamic_cast<decltype(funType)>(&_type))
@ -1694,7 +1695,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall)
{
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
m_context.appendConditionalInvalid();
m_context.appendConditionalRevert();
existenceChecked = true;
}
@ -1730,7 +1731,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
{
//Propagate error condition (if CALL pushes 0 on stack).
m_context << Instruction::ISZERO;
m_context.appendConditionalInvalid();
m_context.appendConditionalRevert();
}
utils().popStackSlots(remainsSize);

View File

@ -28,7 +28,7 @@
#include <libevmasm/SourceLocation.h>
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/codegen/LValue.h>
#include <libsolidity/interface/Utils.h>
#include <libsolidity/interface/Exceptions.h>
namespace dev {
namespace eth

View File

@ -186,7 +186,7 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
solUnimplemented("Not yet implemented - FixedPointType.");
if (m_dataType->category() == Type::Category::FixedBytes)
{
m_context << (u256(0x1) << (256 - 8 * m_dataType->storageBytes())) << Instruction::MUL;
CompilerUtils(m_context).leftShiftNumberOnStack(256 - 8 * m_dataType->storageBytes());
cleaned = true;
}
else if (
@ -267,9 +267,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
else if (m_dataType->category() == Type::Category::FixedBytes)
{
solAssert(_sourceType.category() == Type::Category::FixedBytes, "source not fixed bytes");
m_context
<< (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes()))
<< Instruction::SWAP1 << Instruction::DIV;
CompilerUtils(m_context).rightShiftNumberOnStack(256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes(), false);
}
else
{

View File

@ -1,902 +0,0 @@
/*
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/>.
*/
/**
* @author Christian <c@ethdev.com>
* @date 2015
* Component that translates Solidity code into the why3 programming language.
*/
#include <libsolidity/formal/Why3Translator.h>
#include <boost/algorithm/string/predicate.hpp>
using namespace std;
using namespace dev;
using namespace dev::solidity;
bool Why3Translator::process(SourceUnit const& _source)
{
try
{
if (m_lines.size() != 1 || !m_lines.back().contents.empty())
fatalError(_source, "Multiple source units not yet supported");
appendPreface();
_source.accept(*this);
}
catch (NoFormalType&)
{
solAssert(false, "There is a call to toFormalType() that does not catch NoFormalType exceptions.");
}
catch (FatalError& /*_e*/)
{
solAssert(m_errorOccured, "");
}
return !m_errorOccured;
}
string Why3Translator::translation() const
{
string result;
for (auto const& line: m_lines)
result += string(line.indentation, '\t') + line.contents + "\n";
return result;
}
void Why3Translator::error(ASTNode const& _node, string const& _description)
{
auto err = make_shared<Error>(Error::Type::Why3TranslatorError);
*err <<
errinfo_sourceLocation(_node.location()) <<
errinfo_comment(_description);
m_errors.push_back(err);
m_errorOccured = true;
}
void Why3Translator::fatalError(ASTNode const& _node, string const& _description)
{
error(_node, _description);
BOOST_THROW_EXCEPTION(FatalError());
}
string Why3Translator::toFormalType(Type const& _type) const
{
if (_type.category() == Type::Category::Bool)
return "bool";
else if (auto type = dynamic_cast<IntegerType const*>(&_type))
{
if (!type->isAddress() && !type->isSigned() && type->numBits() == 256)
return "uint256";
}
else if (auto type = dynamic_cast<ArrayType const*>(&_type))
{
if (!type->isByteArray() && type->isDynamicallySized() && type->dataStoredIn(DataLocation::Memory))
{
// Not catching NoFormalType exception. Let the caller deal with it.
string base = toFormalType(*type->baseType());
return "array " + base;
}
}
else if (auto mappingType = dynamic_cast<MappingType const*>(&_type))
{
solAssert(mappingType->keyType(), "A mappingType misses a keyType.");
if (dynamic_cast<IntegerType const*>(&*mappingType->keyType()))
{
//@TODO Use the information from the key type and specify the length of the array as an invariant.
// Also the constructor need to specify the length of the array.
solAssert(mappingType->valueType(), "A mappingType misses a valueType.");
// Not catching NoFormalType exception. Let the caller deal with it.
string valueTypeFormal = toFormalType(*mappingType->valueType());
return "array " + valueTypeFormal;
}
}
BOOST_THROW_EXCEPTION(NoFormalType()
<< errinfo_noFormalTypeFrom(_type.toString(true)));
}
void Why3Translator::addLine(string const& _line)
{
newLine();
add(_line);
newLine();
}
void Why3Translator::add(string const& _str)
{
m_lines.back().contents += _str;
}
void Why3Translator::newLine()
{
if (!m_lines.back().contents.empty())
m_lines.push_back({"", m_lines.back().indentation});
}
void Why3Translator::unindent()
{
newLine();
solAssert(m_lines.back().indentation > 0, "");
m_lines.back().indentation--;
}
bool Why3Translator::visit(ContractDefinition const& _contract)
{
if (m_seenContract)
error(_contract, "More than one contract not supported.");
m_seenContract = true;
m_currentContract.contract = &_contract;
if (_contract.isLibrary())
error(_contract, "Libraries not supported.");
addLine("module Contract_" + _contract.name());
indent();
addLine("use import int.Int");
addLine("use import ref.Ref");
addLine("use import map.Map");
addLine("use import array.Array");
addLine("use import int.ComputerDivision");
addLine("use import mach.int.Unsigned");
addLine("use import UInt256");
addLine("exception Revert");
addLine("exception Return");
if (_contract.stateVariables().empty())
addLine("type state = ()");
else
{
addLine("type state = {");
indent();
m_currentContract.stateVariables = _contract.stateVariables();
for (VariableDeclaration const* variable: m_currentContract.stateVariables)
{
string varType;
try
{
varType = toFormalType(*variable->annotation().type);
}
catch (NoFormalType &err)
{
string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err);
string typeName = typeNamePtr ? " \"" + *typeNamePtr + "\"" : "";
fatalError(*variable, "Type" + typeName + " not supported for state variable.");
}
addLine("mutable _" + variable->name() + ": " + varType);
}
unindent();
addLine("}");
}
addLine("type account = {");
indent();
addLine("mutable balance: uint256;");
addLine("storage: state");
unindent();
addLine("}");
addLine("val external_call (this: account): bool");
indent();
addLine("ensures { result = false -> this = (old this) }");
addLine("writes { this }");
addSourceFromDocStrings(m_currentContract.contract->annotation());
unindent();
if (!_contract.baseContracts().empty())
error(*_contract.baseContracts().front(), "Inheritance not supported.");
if (!_contract.definedStructs().empty())
error(*_contract.definedStructs().front(), "User-defined types not supported.");
if (!_contract.definedEnums().empty())
error(*_contract.definedEnums().front(), "User-defined types not supported.");
if (!_contract.events().empty())
error(*_contract.events().front(), "Events not supported.");
if (!_contract.functionModifiers().empty())
error(*_contract.functionModifiers().front(), "Modifiers not supported.");
ASTNode::listAccept(_contract.definedFunctions(), *this);
return false;
}
void Why3Translator::endVisit(ContractDefinition const&)
{
m_currentContract.reset();
unindent();
addLine("end");
}
bool Why3Translator::visit(FunctionDefinition const& _function)
{
if (!_function.isImplemented())
{
error(_function, "Unimplemented functions not supported.");
return false;
}
if (_function.name().empty())
{
error(_function, "Fallback functions not supported.");
return false;
}
if (!_function.modifiers().empty())
{
error(_function, "Modifiers not supported.");
return false;
}
m_localVariables.clear();
for (auto const& var: _function.parameters())
m_localVariables[var->name()] = var.get();
for (auto const& var: _function.returnParameters())
m_localVariables[var->name()] = var.get();
for (auto const& var: _function.localVariables())
m_localVariables[var->name()] = var;
add("let rec _" + _function.name());
add(" (this: account)");
for (auto const& param: _function.parameters())
{
string paramType;
try
{
paramType = toFormalType(*param->annotation().type);
}
catch (NoFormalType &err)
{
string const* typeName = boost::get_error_info<errinfo_noFormalTypeFrom>(err);
error(*param, "Parameter type \"" + (typeName ? *typeName : "") + "\" not supported.");
}
if (param->name().empty())
error(*param, "Anonymous function parameters not supported.");
add(" (arg_" + param->name() + ": " + paramType + ")");
}
add(":");
indent();
indent();
string retString = "(";
for (auto const& retParam: _function.returnParameters())
{
string paramType;
try
{
paramType = toFormalType(*retParam->annotation().type);
}
catch (NoFormalType &err)
{
string const* typeName = boost::get_error_info<errinfo_noFormalTypeFrom>(err);
error(*retParam, "Parameter type " + (typeName ? *typeName : "") + " not supported.");
}
if (retString.size() != 1)
retString += ", ";
retString += paramType;
}
add(retString + ")");
unindent();
addSourceFromDocStrings(_function.annotation());
if (!m_currentContract.contract)
error(_function, "Only functions inside contracts allowed.");
addSourceFromDocStrings(m_currentContract.contract->annotation());
if (_function.isDeclaredConst())
addLine("ensures { (old this) = this }");
else
addLine("writes { this }");
addLine("=");
// store the prestate in the case we need to revert
addLine("let prestate = {balance = this.balance; storage = " + copyOfStorage() + "} in ");
// initialise local variables
for (auto const& variable: _function.parameters())
addLine("let _" + variable->name() + " = ref arg_" + variable->name() + " in");
for (auto const& variable: _function.returnParameters())
{
if (variable->name().empty())
error(*variable, "Unnamed return variables not yet supported.");
string varType;
try
{
varType = toFormalType(*variable->annotation().type);
}
catch (NoFormalType &err)
{
string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err);
error(*variable, "Type " + (typeNamePtr ? *typeNamePtr : "") + "in return parameter not yet supported.");
}
addLine("let _" + variable->name() + ": ref " + varType + " = ref (of_int 0) in");
}
for (VariableDeclaration const* variable: _function.localVariables())
{
if (variable->name().empty())
error(*variable, "Unnamed variables not yet supported.");
string varType;
try
{
varType = toFormalType(*variable->annotation().type);
}
catch (NoFormalType &err)
{
string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err);
error(*variable, "Type " + (typeNamePtr ? *typeNamePtr : "") + "in variable declaration not yet supported.");
}
addLine("let _" + variable->name() + ": ref " + varType + " = ref (of_int 0) in");
}
addLine("try");
_function.body().accept(*this);
add(";");
addLine("raise Return");
string retVals;
for (auto const& variable: _function.returnParameters())
{
if (!retVals.empty())
retVals += ", ";
retVals += "!_" + variable->name();
}
addLine("with Return -> (" + retVals + ") |");
string reversion = " Revert -> this.balance <- prestate.balance; ";
for (auto const* variable: m_currentContract.stateVariables)
reversion += "this.storage._" + variable->name() + " <- prestate.storage._" + variable->name() + "; ";
//@TODO in case of reversion the return values are wrong - we need to change the
// return type to include a bool to signify if an exception was thrown.
reversion += "(" + retVals + ")";
addLine(reversion);
unindent();
addLine("end");
addLine("");
return false;
}
void Why3Translator::endVisit(FunctionDefinition const&)
{
m_localVariables.clear();
}
bool Why3Translator::visit(Block const& _node)
{
addSourceFromDocStrings(_node.annotation());
add("begin");
indent();
for (size_t i = 0; i < _node.statements().size(); ++i)
{
_node.statements()[i]->accept(*this);
if (i != _node.statements().size() - 1)
{
auto it = m_lines.end() - 1;
while (it != m_lines.begin() && it->contents.empty())
--it;
if (!boost::algorithm::ends_with(it->contents, "begin"))
it->contents += ";";
}
newLine();
}
unindent();
add("end");
return false;
}
bool Why3Translator::visit(IfStatement const& _node)
{
addSourceFromDocStrings(_node.annotation());
add("if ");
_node.condition().accept(*this);
add(" then");
visitIndentedUnlessBlock(_node.trueStatement());
if (_node.falseStatement())
{
newLine();
add("else");
visitIndentedUnlessBlock(*_node.falseStatement());
}
return false;
}
bool Why3Translator::visit(WhileStatement const& _node)
{
addSourceFromDocStrings(_node.annotation());
// Why3 does not appear to support do-while loops,
// so we will simulate them by performing a while
// loop with the body prepended once.
if (_node.isDoWhile())
{
visitIndentedUnlessBlock(_node.body());
newLine();
}
add("while ");
_node.condition().accept(*this);
newLine();
add("do");
visitIndentedUnlessBlock(_node.body());
add("done");
return false;
}
bool Why3Translator::visit(Return const& _node)
{
addSourceFromDocStrings(_node.annotation());
if (_node.expression())
{
solAssert(!!_node.annotation().functionReturnParameters, "");
auto const& params = _node.annotation().functionReturnParameters->parameters();
if (params.size() != 1)
{
error(_node, "Directly returning tuples not supported. Rather assign to return variable.");
return false;
}
add("begin _" + params.front()->name() + " := ");
_node.expression()->accept(*this);
add("; raise Return end");
}
else
add("raise Return");
return false;
}
bool Why3Translator::visit(Throw const& _node)
{
addSourceFromDocStrings(_node.annotation());
add("raise Revert");
return false;
}
bool Why3Translator::visit(VariableDeclarationStatement const& _node)
{
addSourceFromDocStrings(_node.annotation());
if (_node.declarations().size() != 1)
{
error(_node, "Multiple variables not supported.");
return false;
}
if (_node.initialValue())
{
add("_" + _node.declarations().front()->name() + " := ");
_node.initialValue()->accept(*this);
}
return false;
}
bool Why3Translator::visit(ExpressionStatement const& _node)
{
addSourceFromDocStrings(_node.annotation());
return true;
}
bool Why3Translator::visit(Assignment const& _node)
{
if (_node.assignmentOperator() != Token::Assign)
error(_node, "Compound assignment not supported.");
_node.leftHandSide().accept(*this);
add(m_currentLValueIsRef ? " := " : " <- ");
_node.rightHandSide().accept(*this);
return false;
}
bool Why3Translator::visit(TupleExpression const& _node)
{
if (_node.components().size() != 1)
error(_node, "Only tuples with exactly one component supported.");
add("(");
return true;
}
bool Why3Translator::visit(UnaryOperation const& _unaryOperation)
{
try
{
toFormalType(*_unaryOperation.annotation().type);
}
catch (NoFormalType &err)
{
string const* typeNamePtr = boost::get_error_info<errinfo_noFormalTypeFrom>(err);
error(_unaryOperation, "Type \"" + (typeNamePtr ? *typeNamePtr : "") + "\" supported in unary operation.");
}
switch (_unaryOperation.getOperator())
{
case Token::Not: // !
add("(not ");
break;
default:
error(_unaryOperation, "Operator not supported.");
break;
}
_unaryOperation.subExpression().accept(*this);
add(")");
return false;
}
bool Why3Translator::visit(BinaryOperation const& _binaryOperation)
{
Expression const& leftExpression = _binaryOperation.leftExpression();
Expression const& rightExpression = _binaryOperation.rightExpression();
solAssert(!!_binaryOperation.annotation().commonType, "");
Type const& commonType = *_binaryOperation.annotation().commonType;
Token::Value const c_op = _binaryOperation.getOperator();
if (commonType.category() == Type::Category::RationalNumber)
{
auto const& constantNumber = dynamic_cast<RationalNumberType const&>(commonType);
if (constantNumber.isFractional())
error(_binaryOperation, "Fractional numbers not supported.");
else
add("(of_int " + toString(commonType.literalValue(nullptr)) + ")");
return false;
}
static const map<Token::Value, char const*> optrans({
{Token::And, " && "},
{Token::Or, " || "},
{Token::BitOr, " lor "},
{Token::BitXor, " lxor "},
{Token::BitAnd, " land "},
{Token::Add, " + "},
{Token::Sub, " - "},
{Token::Mul, " * "},
{Token::Div, " / "},
{Token::Mod, " mod "},
{Token::Equal, " = "},
{Token::NotEqual, " <> "},
{Token::LessThan, " < "},
{Token::GreaterThan, " > "},
{Token::LessThanOrEqual, " <= "},
{Token::GreaterThanOrEqual, " >= "}
});
if (!optrans.count(c_op))
{
error(_binaryOperation, "Operator not supported.");
return true;
}
add("(");
leftExpression.accept(*this);
add(optrans.at(c_op));
rightExpression.accept(*this);
add(")");
return false;
}
bool Why3Translator::visit(FunctionCall const& _node)
{
if (_node.annotation().isTypeConversion || _node.annotation().isStructConstructorCall)
{
error(_node, "Only ordinary function calls supported.");
return true;
}
FunctionType const& function = dynamic_cast<FunctionType const&>(*_node.expression().annotation().type);
switch (function.kind())
{
case FunctionType::Kind::AddMod:
case FunctionType::Kind::MulMod:
{
//@todo require that third parameter is not zero
add("(of_int (mod (Int.(");
add(function.kind() == FunctionType::Kind::AddMod ? "+" : "*");
add(") (to_int ");
_node.arguments().at(0)->accept(*this);
add(") (to_int ");
_node.arguments().at(1)->accept(*this);
add(")) (to_int ");
_node.arguments().at(2)->accept(*this);
add(")))");
return false;
}
case FunctionType::Kind::Internal:
{
if (!_node.names().empty())
{
error(_node, "Function calls with named arguments not supported.");
return true;
}
//@TODO check type conversions
add("(");
_node.expression().accept(*this);
add(" state");
for (auto const& arg: _node.arguments())
{
add(" ");
arg->accept(*this);
}
add(")");
return false;
}
case FunctionType::Kind::Bare:
{
if (!_node.arguments().empty())
{
error(_node, "Function calls with named arguments not supported.");
return true;
}
add("(");
indent();
add("let amount = 0 in ");
_node.expression().accept(*this);
addLine("if amount <= this.balance then");
indent();
addLine("let balance_precall = this.balance in");
addLine("begin");
indent();
addLine("this.balance <- this.balance - amount;");
addLine("if not (external_call this) then begin this.balance = balance_precall; false end else true");
unindent();
addLine("end");
unindent();
addLine("else false");
unindent();
add(")");
return false;
}
case FunctionType::Kind::SetValue:
{
add("let amount = ");
solAssert(_node.arguments().size() == 1, "");
_node.arguments()[0]->accept(*this);
add(" in ");
return false;
}
default:
error(_node, "Only internal function calls supported.");
return true;
}
}
bool Why3Translator::visit(MemberAccess const& _node)
{
if (
_node.expression().annotation().type->category() == Type::Category::Array &&
_node.memberName() == "length" &&
!_node.annotation().lValueRequested
)
{
add("(of_int ");
_node.expression().accept(*this);
add(".length");
add(")");
}
else if (
_node.memberName() == "call" &&
*_node.expression().annotation().type == IntegerType(160, IntegerType::Modifier::Address)
)
{
// Do nothing, do not even visit the address because this will be an external call
//@TODO ensure that the expression itself does not have side-effects
return false;
}
else
error(_node, "Member access: Only call and array length supported.");
return false;
}
bool Why3Translator::visit(IndexAccess const& _node)
{
auto baseType = dynamic_cast<ArrayType const*>(_node.baseExpression().annotation().type.get());
if (!baseType)
{
error(_node, "Index access only supported for arrays.");
return true;
}
if (_node.annotation().lValueRequested)
{
error(_node, "Assignment to array elements not supported.");
return true;
}
add("(");
_node.baseExpression().accept(*this);
add("[to_int ");
_node.indexExpression()->accept(*this);
add("]");
add(")");
return false;
}
bool Why3Translator::visit(Identifier const& _identifier)
{
Declaration const* declaration = _identifier.annotation().referencedDeclaration;
if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
add("_" + functionDef->name());
else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration))
{
bool isStateVar = isStateVariable(variable);
bool lvalue = _identifier.annotation().lValueRequested;
if (isStateVar)
add("this.storage.");
else if (!lvalue)
add("!(");
add("_" + variable->name());
if (!isStateVar && !lvalue)
add(")");
m_currentLValueIsRef = !isStateVar;
}
else
error(_identifier, "Not supported.");
return false;
}
bool Why3Translator::visit(Literal const& _literal)
{
TypePointer type = _literal.annotation().type;
switch (type->category())
{
case Type::Category::Bool:
if (type->literalValue(&_literal) == 0)
add("false");
else
add("true");
break;
case Type::Category::RationalNumber:
{
auto const& constantNumber = dynamic_cast<RationalNumberType const&>(*type);
if (constantNumber.isFractional())
error(_literal, "Fractional numbers not supported.");
else
add("(of_int " + toString(type->literalValue(&_literal)) + ")");
break;
}
default:
error(_literal, "Not supported.");
}
return false;
}
bool Why3Translator::visit(PragmaDirective const& _pragma)
{
if (_pragma.tokens().empty())
error(_pragma, "Not supported");
else if (_pragma.literals().empty())
error(_pragma, "Not supported");
else if (_pragma.literals()[0] != "solidity")
error(_pragma, "Not supported");
else if (_pragma.tokens()[0] != Token::Identifier)
error(_pragma, "A literal 'solidity' is not an identifier. Strange");
return false;
}
bool Why3Translator::isStateVariable(VariableDeclaration const* _var) const
{
return contains(m_currentContract.stateVariables, _var);
}
bool Why3Translator::isStateVariable(string const& _name) const
{
for (auto const& var: m_currentContract.stateVariables)
if (var->name() == _name)
return true;
return false;
}
bool Why3Translator::isLocalVariable(VariableDeclaration const* _var) const
{
for (auto const& var: m_localVariables)
if (var.second == _var)
return true;
return false;
}
bool Why3Translator::isLocalVariable(string const& _name) const
{
return m_localVariables.count(_name);
}
string Why3Translator::copyOfStorage() const
{
if (m_currentContract.stateVariables.empty())
return "()";
string ret = "{";
bool first = true;
for (auto const* variable: m_currentContract.stateVariables)
{
if (first)
first = false;
else
ret += "; ";
ret += "_" + variable->name() + " = this.storage._" + variable->name();
}
return ret + "}";
}
void Why3Translator::visitIndentedUnlessBlock(Statement const& _statement)
{
bool isBlock = !!dynamic_cast<Block const*>(&_statement);
if (isBlock)
newLine();
else
indent();
_statement.accept(*this);
if (isBlock)
newLine();
else
unindent();
}
void Why3Translator::addSourceFromDocStrings(DocumentedAnnotation const& _annotation)
{
auto why3Range = _annotation.docTags.equal_range("why3");
for (auto i = why3Range.first; i != why3Range.second; ++i)
addLine(transformVariableReferences(i->second.content));
}
string Why3Translator::transformVariableReferences(string const& _annotation)
{
string ret;
auto pos = _annotation.begin();
while (true)
{
auto hash = find(pos, _annotation.end(), '#');
ret.append(pos, hash);
if (hash == _annotation.end())
break;
auto hashEnd = find_if(hash + 1, _annotation.end(), [](char _c)
{
return
(_c != '_' && _c != '$') &&
!('a' <= _c && _c <= 'z') &&
!('A' <= _c && _c <= 'Z') &&
!('0' <= _c && _c <= '9');
});
string varName(hash + 1, hashEnd);
if (isLocalVariable(varName))
ret += "(!_" + varName + ")";
else if (isStateVariable(varName))
ret += "(this.storage._" + varName + ")";
//@todo return variables
else
ret.append(hash, hashEnd);
pos = hashEnd;
}
return ret;
}
void Why3Translator::appendPreface()
{
m_lines.push_back(Line{R"(
module UInt256
use import mach.int.Unsigned
type uint256
constant max_uint256: int = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
clone export mach.int.Unsigned with
type t = uint256,
constant max = max_uint256
end
module Address
use import mach.int.Unsigned
type address
constant max_address: int = 0xffffffffffffffffffffffffffffffffffffffff (* 160 bit = 40 f's *)
clone export mach.int.Unsigned with
type t = address,
constant max = max_address
end
)", 0});
}

View File

@ -1,149 +0,0 @@
/*
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/>.
*/
/**
* @author Christian <c@ethdev.com>
* @date 2015
* Component that translates Solidity code into the why3 programming language.
*/
#pragma once
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/interface/Exceptions.h>
#include <string>
namespace dev
{
namespace solidity
{
class SourceUnit;
/**
* Simple translator from Solidity to Why3.
*
* @todo detect side effects in sub-expressions and limit them to one per statement. #1043
* @todo `x = y = z`
* @todo implicit and explicit type conversion
*/
class Why3Translator: private ASTConstVisitor
{
public:
Why3Translator(ErrorList& _errors): m_lines(std::vector<Line>{{std::string(), 0}}), m_errors(_errors) {}
/// Appends formalisation of the given source unit to the output.
/// @returns false on error.
bool process(SourceUnit const& _source);
std::string translation() const;
private:
/// Creates an error and adds it to errors list.
void error(ASTNode const& _node, std::string const& _description);
/// Reports a fatal error and throws.
void fatalError(ASTNode const& _node, std::string const& _description);
/// Appends imports and constants use throughout the formal code.
void appendPreface();
/// @returns a string representation of the corresponding formal type or throws NoFormalType exception.
std::string toFormalType(Type const& _type) const;
using errinfo_noFormalTypeFrom = boost::error_info<struct tag_noFormalTypeFrom, std::string /* name of the type that cannot be translated */ >;
struct NoFormalType: virtual Exception {};
void indent() { newLine(); m_lines.back().indentation++; }
void unindent();
void addLine(std::string const& _line);
void add(std::string const& _str);
void newLine();
void appendSemicolon();
virtual bool visit(SourceUnit const&) override { return true; }
virtual bool visit(ContractDefinition const& _contract) override;
virtual void endVisit(ContractDefinition const& _contract) override;
virtual bool visit(FunctionDefinition const& _function) override;
virtual void endVisit(FunctionDefinition const& _function) override;
virtual bool visit(Block const&) override;
virtual bool visit(IfStatement const& _node) override;
virtual bool visit(WhileStatement const& _node) override;
virtual bool visit(Return const& _node) override;
virtual bool visit(Throw const& _node) override;
virtual bool visit(VariableDeclarationStatement const& _node) override;
virtual bool visit(ExpressionStatement const&) override;
virtual bool visit(Assignment const& _node) override;
virtual bool visit(TupleExpression const& _node) override;
virtual void endVisit(TupleExpression const&) override { add(")"); }
virtual bool visit(UnaryOperation const& _node) override;
virtual bool visit(BinaryOperation const& _node) override;
virtual bool visit(FunctionCall const& _node) override;
virtual bool visit(MemberAccess const& _node) override;
virtual bool visit(IndexAccess const& _node) override;
virtual bool visit(Identifier const& _node) override;
virtual bool visit(Literal const& _node) override;
virtual bool visit(PragmaDirective const& _node) override;
virtual bool visitNode(ASTNode const& _node) override
{
error(_node, "Code not supported for formal verification.");
return false;
}
bool isStateVariable(VariableDeclaration const* _var) const;
bool isStateVariable(std::string const& _name) const;
bool isLocalVariable(VariableDeclaration const* _var) const;
bool isLocalVariable(std::string const& _name) const;
/// @returns a string representing an expression that is a copy of this.storage
std::string copyOfStorage() const;
/// Visits the given statement and indents it unless it is a block
/// (which does its own indentation).
void visitIndentedUnlessBlock(Statement const& _statement);
void addSourceFromDocStrings(DocumentedAnnotation const& _annotation);
/// Transforms substring like `#varName` and `#stateVarName` to code that evaluates to their value.
std::string transformVariableReferences(std::string const& _annotation);
/// True if we have already seen a contract. For now, only a single contract
/// is supported.
bool m_seenContract = false;
bool m_errorOccured = false;
/// Metadata relating to the current contract
struct ContractMetadata
{
ContractDefinition const* contract = nullptr;
std::vector<VariableDeclaration const*> stateVariables;
void reset() { contract = nullptr; stateVariables.clear(); }
};
ContractMetadata m_currentContract;
bool m_currentLValueIsRef = false;
std::map<std::string, VariableDeclaration const*> m_localVariables;
struct Line
{
std::string contents;
unsigned indentation;
};
std::vector<Line> m_lines;
ErrorList& m_errors;
};
}
}

View File

@ -25,10 +25,10 @@
#include <libsolidity/inlineasm/AsmScope.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/interface/Utils.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <boost/range/adaptor/reversed.hpp>
#include <boost/algorithm/string.hpp>
#include <memory>
#include <functional>
@ -38,18 +38,15 @@ using namespace dev;
using namespace dev::solidity;
using namespace dev::solidity::assembly;
AsmAnalyzer::AsmAnalyzer(
AsmAnalysisInfo& _analysisInfo,
ErrorList& _errors,
ExternalIdentifierAccess::Resolver const& _resolver
):
m_resolver(_resolver), m_info(_analysisInfo), m_errors(_errors)
{
namespace {
set<string> const builtinTypes{"bool", "u8", "s8", "u32", "s32", "u64", "s64", "u128", "s128", "u256", "s256"};
}
bool AsmAnalyzer::analyze(Block const& _block)
{
if (!(ScopeFiller(m_info.scopes, m_errors))(_block))
if (!(ScopeFiller(m_info, m_errorReporter))(_block))
return false;
return (*this)(_block);
@ -57,28 +54,31 @@ bool AsmAnalyzer::analyze(Block const& _block)
bool AsmAnalyzer::operator()(Label const& _label)
{
solAssert(!m_julia, "");
m_info.stackHeightInfo[&_label] = m_stackHeight;
return true;
}
bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction)
{
solAssert(!m_julia, "");
auto const& info = instructionInfo(_instruction.instruction);
m_stackHeight += info.ret - info.args;
m_info.stackHeightInfo[&_instruction] = m_stackHeight;
warnOnInstructions(_instruction.instruction, _instruction.location);
return true;
}
bool AsmAnalyzer::operator()(assembly::Literal const& _literal)
{
expectValidType(_literal.type, _literal.location);
++m_stackHeight;
if (!_literal.isNumber && _literal.value.size() > 32)
if (_literal.kind == assembly::LiteralKind::String && _literal.value.size() > 32)
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)",
_literal.location
));
m_errorReporter.typeError(
_literal.location,
"String literal too long (" + boost::lexical_cast<std::string>(_literal.value.size()) + " > 32)"
);
return false;
}
m_info.stackHeightInfo[&_literal] = m_stackHeight;
@ -87,18 +87,17 @@ bool AsmAnalyzer::operator()(assembly::Literal const& _literal)
bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
{
size_t numErrorsBefore = m_errors.size();
size_t numErrorsBefore = m_errorReporter.errors().size();
bool success = true;
if (m_currentScope->lookup(_identifier.name, Scope::Visitor(
[&](Scope::Variable const& _var)
{
if (!_var.active)
if (!m_activeVariables.count(&_var))
{
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Variable " + _identifier.name + " used before it was declared.",
_identifier.location
));
m_errorReporter.declarationError(
_identifier.location,
"Variable " + _identifier.name + " used before it was declared."
);
success = false;
}
++m_stackHeight;
@ -109,11 +108,10 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
},
[&](Scope::Function const&)
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Function " + _identifier.name + " used without being called.",
_identifier.location
));
m_errorReporter.typeError(
_identifier.location,
"Function " + _identifier.name + " used without being called."
);
success = false;
}
)))
@ -123,16 +121,15 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
{
size_t stackSize(-1);
if (m_resolver)
stackSize = m_resolver(_identifier, IdentifierContext::RValue);
{
bool insideFunction = m_currentScope->insideFunction();
stackSize = m_resolver(_identifier, julia::IdentifierContext::RValue, insideFunction);
}
if (stackSize == size_t(-1))
{
// Only add an error message if the callback did not do it.
if (numErrorsBefore == m_errors.size())
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Identifier not found.",
_identifier.location
));
if (numErrorsBefore == m_errorReporter.errors().size())
m_errorReporter.declarationError(_identifier.location, "Identifier not found.");
success = false;
}
m_stackHeight += stackSize == size_t(-1) ? 1 : stackSize;
@ -143,15 +140,11 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
{
solAssert(!m_julia, "");
bool success = true;
for (auto const& arg: _instr.arguments | boost::adaptors::reversed)
{
int const stackHeight = m_stackHeight;
if (!boost::apply_visitor(*this, arg))
if (!expectExpression(arg))
success = false;
if (!expectDeposit(1, stackHeight, locationOf(arg)))
success = false;
}
// Parser already checks that the number of arguments is correct.
solAssert(instructionInfo(_instr.instruction.instruction).args == int(_instr.arguments.size()), "");
if (!(*this)(_instr.instruction))
@ -160,15 +153,15 @@ bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
return success;
}
bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment)
bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment)
{
solAssert(!m_julia, "");
bool success = checkAssignment(_assignment.variableName, size_t(-1));
m_info.stackHeightInfo[&_assignment] = m_stackHeight;
return success;
}
bool AsmAnalyzer::operator()(FunctionalAssignment const& _assignment)
bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment)
{
int const stackHeight = m_stackHeight;
bool success = boost::apply_visitor(*this, *_assignment.value);
@ -181,23 +174,37 @@ bool AsmAnalyzer::operator()(FunctionalAssignment const& _assignment)
bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl)
{
int const expectedItems = _varDecl.variables.size();
int const stackHeight = m_stackHeight;
bool success = boost::apply_visitor(*this, *_varDecl.value);
solAssert(m_stackHeight - stackHeight == 1, "Invalid value size.");
boost::get<Scope::Variable>(m_currentScope->identifiers.at(_varDecl.name)).active = true;
if ((m_stackHeight - stackHeight) != expectedItems)
{
m_errorReporter.declarationError(_varDecl.location, "Variable count mismatch.");
return false;
}
for (auto const& variable: _varDecl.variables)
{
expectValidType(variable.type, variable.location);
m_activeVariables.insert(&boost::get<Scope::Variable>(m_currentScope->identifiers.at(variable.name)));
}
m_info.stackHeightInfo[&_varDecl] = m_stackHeight;
return success;
}
bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef)
{
Scope& bodyScope = scope(&_funDef.body);
Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get();
solAssert(virtualBlock, "");
Scope& varScope = scope(virtualBlock);
for (auto const& var: _funDef.arguments + _funDef.returns)
boost::get<Scope::Variable>(bodyScope.identifiers.at(var)).active = true;
{
expectValidType(var.type, var.location);
m_activeVariables.insert(&boost::get<Scope::Variable>(varScope.identifiers.at(var.name)));
}
int const stackHeight = m_stackHeight;
m_stackHeight = _funDef.arguments.size() + _funDef.returns.size();
m_virtualVariablesInNextBlock = m_stackHeight;
bool success = (*this)(_funDef.body);
@ -214,141 +221,213 @@ bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall)
if (!m_currentScope->lookup(_funCall.functionName.name, Scope::Visitor(
[&](Scope::Variable const&)
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Attempt to call variable instead of function.",
_funCall.functionName.location
));
m_errorReporter.typeError(
_funCall.functionName.location,
"Attempt to call variable instead of function."
);
success = false;
},
[&](Scope::Label const&)
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Attempt to call label instead of function.",
_funCall.functionName.location
));
m_errorReporter.typeError(
_funCall.functionName.location,
"Attempt to call label instead of function."
);
success = false;
},
[&](Scope::Function const& _fun)
{
arguments = _fun.arguments;
returns = _fun.returns;
/// TODO: compare types too
arguments = _fun.arguments.size();
returns = _fun.returns.size();
}
)))
{
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Function not found.",
_funCall.functionName.location
));
m_errorReporter.declarationError(_funCall.functionName.location, "Function not found.");
success = false;
}
if (success)
{
if (_funCall.arguments.size() != arguments)
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Expected " +
boost::lexical_cast<string>(arguments) +
" arguments but got " +
boost::lexical_cast<string>(_funCall.arguments.size()) +
".",
_funCall.functionName.location
));
m_errorReporter.typeError(
_funCall.functionName.location,
"Expected " + boost::lexical_cast<string>(arguments) + " arguments but got " +
boost::lexical_cast<string>(_funCall.arguments.size()) + "."
);
success = false;
}
}
for (auto const& arg: _funCall.arguments | boost::adaptors::reversed)
{
int const stackHeight = m_stackHeight;
if (!boost::apply_visitor(*this, arg))
if (!expectExpression(arg))
success = false;
if (!expectDeposit(1, stackHeight, locationOf(arg)))
success = false;
}
m_stackHeight += int(returns) - int(arguments);
m_info.stackHeightInfo[&_funCall] = m_stackHeight;
return success;
}
bool AsmAnalyzer::operator()(Switch const& _switch)
{
bool success = true;
if (!expectExpression(*_switch.expression))
success = false;
set<tuple<LiteralKind, string>> cases;
for (auto const& _case: _switch.cases)
{
if (_case.value)
{
int const initialStackHeight = m_stackHeight;
// We cannot use "expectExpression" here because *_case.value is not a
// Statement and would be converted to a Statement otherwise.
if (!(*this)(*_case.value))
success = false;
expectDeposit(1, initialStackHeight, _case.value->location);
m_stackHeight--;
/// Note: the parser ensures there is only one default case
auto val = make_tuple(_case.value->kind, _case.value->value);
if (!cases.insert(val).second)
{
m_errorReporter.declarationError(
_case.location,
"Duplicate case defined"
);
success = false;
}
}
if (!(*this)(_case.body))
success = false;
}
m_stackHeight--;
m_info.stackHeightInfo[&_switch] = m_stackHeight;
return success;
}
bool AsmAnalyzer::operator()(assembly::ForLoop const& _for)
{
Scope* originalScope = m_currentScope;
bool success = true;
if (!(*this)(_for.pre))
success = false;
// The block was closed already, but we re-open it again and stuff the
// condition, the body and the post part inside.
m_stackHeight += scope(&_for.pre).numberOfVariables();
m_currentScope = &scope(&_for.pre);
if (!expectExpression(*_for.condition))
success = false;
m_stackHeight--;
if (!(*this)(_for.body))
success = false;
if (!(*this)(_for.post))
success = false;
m_stackHeight -= scope(&_for.pre).numberOfVariables();
m_info.stackHeightInfo[&_for] = m_stackHeight;
m_currentScope = originalScope;
return success;
}
bool AsmAnalyzer::operator()(Block const& _block)
{
bool success = true;
auto previousScope = m_currentScope;
m_currentScope = &scope(&_block);
int const initialStackHeight = m_stackHeight - m_virtualVariablesInNextBlock;
m_virtualVariablesInNextBlock = 0;
int const initialStackHeight = m_stackHeight;
for (auto const& s: _block.statements)
if (!boost::apply_visitor(*this, s))
success = false;
for (auto const& identifier: scope(&_block).identifiers)
if (identifier.second.type() == typeid(Scope::Variable))
--m_stackHeight;
m_stackHeight -= scope(&_block).numberOfVariables();
int const stackDiff = m_stackHeight - initialStackHeight;
if (stackDiff != 0)
{
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
m_errorReporter.declarationError(
_block.location,
"Unbalanced stack at the end of a block: " +
(
stackDiff > 0 ?
to_string(stackDiff) + string(" surplus item(s).") :
to_string(-stackDiff) + string(" missing item(s).")
),
_block.location
));
)
);
success = false;
}
m_currentScope = m_currentScope->superScope;
m_info.stackHeightInfo[&_block] = m_stackHeight;
m_currentScope = previousScope;
return success;
}
bool AsmAnalyzer::expectExpression(Statement const& _statement)
{
bool success = true;
int const initialHeight = m_stackHeight;
if (!boost::apply_visitor(*this, _statement))
success = false;
if (!expectDeposit(1, initialHeight, locationOf(_statement)))
success = false;
return success;
}
bool AsmAnalyzer::expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location)
{
if (m_stackHeight - _oldHeight != _deposit)
{
m_errorReporter.typeError(
_location,
"Expected expression to return one item to the stack, but did return " +
boost::lexical_cast<string>(m_stackHeight - _oldHeight) +
" items."
);
return false;
}
return true;
}
bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t _valueSize)
{
bool success = true;
size_t numErrorsBefore = m_errors.size();
size_t numErrorsBefore = m_errorReporter.errors().size();
size_t variableSize(-1);
if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name))
{
// Check that it is a variable
if (var->type() != typeid(Scope::Variable))
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Assignment requires variable.",
_variable.location
));
m_errorReporter.typeError(_variable.location, "Assignment requires variable.");
success = false;
}
else if (!boost::get<Scope::Variable>(*var).active)
else if (!m_activeVariables.count(&boost::get<Scope::Variable>(*var)))
{
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Variable " + _variable.name + " used before it was declared.",
_variable.location
));
m_errorReporter.declarationError(
_variable.location,
"Variable " + _variable.name + " used before it was declared."
);
success = false;
}
variableSize = 1;
}
else if (m_resolver)
variableSize = m_resolver(_variable, IdentifierContext::LValue);
{
bool insideFunction = m_currentScope->insideFunction();
variableSize = m_resolver(_variable, julia::IdentifierContext::LValue, insideFunction);
}
if (variableSize == size_t(-1))
{
// Only add message if the callback did not.
if (numErrorsBefore == m_errors.size())
m_errors.push_back(make_shared<Error>(
Error::Type::DeclarationError,
"Variable not found or variable not lvalue.",
_variable.location
));
if (numErrorsBefore == m_errorReporter.errors().size())
m_errorReporter.declarationError(_variable.location, "Variable not found or variable not lvalue.");
success = false;
}
if (_valueSize == size_t(-1))
@ -358,43 +437,60 @@ bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t
if (_valueSize != variableSize && variableSize != size_t(-1))
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
m_errorReporter.typeError(
_variable.location,
"Variable size (" +
to_string(variableSize) +
") and value size (" +
to_string(_valueSize) +
") do not match.",
_variable.location
));
") do not match."
);
success = false;
}
return success;
}
bool AsmAnalyzer::expectDeposit(int const _deposit, int const _oldHeight, SourceLocation const& _location)
{
int stackDiff = m_stackHeight - _oldHeight;
if (stackDiff != _deposit)
{
m_errors.push_back(make_shared<Error>(
Error::Type::TypeError,
"Expected instruction(s) to deposit " +
boost::lexical_cast<string>(_deposit) +
" item(s) to the stack, but did deposit " +
boost::lexical_cast<string>(stackDiff) +
" item(s).",
_location
));
return false;
}
else
return true;
}
Scope& AsmAnalyzer::scope(Block const* _block)
{
solAssert(m_info.scopes.count(_block) == 1, "Scope requested but not present.");
auto scopePtr = m_info.scopes.at(_block);
solAssert(scopePtr, "Scope requested but not present.");
return *scopePtr;
}
void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _location)
{
if (!m_julia)
return;
if (!builtinTypes.count(type))
m_errorReporter.typeError(
_location,
"\"" + type + "\" is not a valid type (user defined types are not yet supported)."
);
}
void AsmAnalyzer::warnOnInstructions(solidity::Instruction _instr, SourceLocation const& _location)
{
static set<solidity::Instruction> futureInstructions{
solidity::Instruction::CREATE2,
solidity::Instruction::RETURNDATACOPY,
solidity::Instruction::RETURNDATASIZE,
solidity::Instruction::STATICCALL
};
if (futureInstructions.count(_instr))
m_errorReporter.warning(
_location,
"The \"" +
boost::to_lower_copy(instructionInfo(_instr).name)
+ "\" instruction is only available after " +
"the Metropolis hard fork. Before that it acts as an invalid instruction."
);
if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI)
m_errorReporter.warning(
_location,
"Jump instructions are low-level EVM features that can lead to "
"incorrect stack access. Because of that they are discouraged. "
"Please consider using \"switch\" or \"for\" statements instead."
);
}

View File

@ -20,10 +20,14 @@
#pragma once
#include <libsolidity/inlineasm/AsmStack.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/inlineasm/AsmScope.h>
#include <libjulia/backends/evm/AbstractAssembly.h>
#include <libsolidity/inlineasm/AsmDataForward.h>
#include <boost/variant.hpp>
#include <functional>
@ -33,23 +37,10 @@ namespace dev
{
namespace solidity
{
class ErrorReporter;
namespace assembly
{
struct Literal;
struct Block;
struct Label;
struct FunctionalInstruction;
struct FunctionalAssignment;
struct VariableDeclaration;
struct Instruction;
struct Identifier;
struct Assignment;
struct FunctionDefinition;
struct FunctionCall;
struct Scope;
struct AsmAnalysisInfo;
/**
@ -60,11 +51,12 @@ struct AsmAnalysisInfo;
class AsmAnalyzer: public boost::static_visitor<bool>
{
public:
AsmAnalyzer(
explicit AsmAnalyzer(
AsmAnalysisInfo& _analysisInfo,
ErrorList& _errors,
ExternalIdentifierAccess::Resolver const& _resolver = ExternalIdentifierAccess::Resolver()
);
ErrorReporter& _errorReporter,
bool _julia = false,
julia::ExternalIdentifierAccess::Resolver const& _resolver = julia::ExternalIdentifierAccess::Resolver()
): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_julia(_julia) {}
bool analyze(assembly::Block const& _block);
@ -73,29 +65,37 @@ public:
bool operator()(assembly::Identifier const&);
bool operator()(assembly::FunctionalInstruction const& _functionalInstruction);
bool operator()(assembly::Label const& _label);
bool operator()(assembly::Assignment const&);
bool operator()(assembly::FunctionalAssignment const& _functionalAssignment);
bool operator()(assembly::StackAssignment const&);
bool operator()(assembly::Assignment const& _assignment);
bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
bool operator()(assembly::FunctionDefinition const& _functionDefinition);
bool operator()(assembly::FunctionCall const& _functionCall);
bool operator()(assembly::Switch const& _switch);
bool operator()(assembly::ForLoop const& _forLoop);
bool operator()(assembly::Block const& _block);
private:
/// Visits the statement and expects it to deposit one item onto the stack.
bool expectExpression(Statement const& _statement);
bool expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location);
/// Verifies that a variable to be assigned to exists and has the same size
/// as the value, @a _valueSize, unless that is equal to -1.
bool checkAssignment(assembly::Identifier const& _assignment, size_t _valueSize = size_t(-1));
bool expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location);
Scope& scope(assembly::Block const* _block);
/// This is used when we enter the body of a function definition. There, the parameters
/// and return parameters appear as variables which are already on the stack before
/// we enter the block.
int m_virtualVariablesInNextBlock = 0;
Scope& scope(assembly::Block const* _block);
void expectValidType(std::string const& type, SourceLocation const& _location);
void warnOnInstructions(solidity::Instruction _instr, SourceLocation const& _location);
int m_stackHeight = 0;
ExternalIdentifierAccess::Resolver const& m_resolver;
julia::ExternalIdentifierAccess::Resolver m_resolver;
Scope* m_currentScope = nullptr;
/// Variables that are active at the current point in assembly (as opposed to
/// "part of the scope but not yet declared")
std::set<Scope::Variable const*> m_activeVariables;
AsmAnalysisInfo& m_info;
ErrorList& m_errors;
ErrorReporter& m_errorReporter;
bool m_julia = false;
};
}

View File

@ -20,10 +20,13 @@
#pragma once
#include <libsolidity/inlineasm/AsmDataForward.h>
#include <boost/variant.hpp>
#include <map>
#include <memory>
#include <vector>
namespace dev
{
@ -32,28 +35,16 @@ namespace solidity
namespace assembly
{
struct Literal;
struct Block;
struct Label;
struct FunctionalInstruction;
struct FunctionalAssignment;
struct VariableDeclaration;
struct Instruction;
struct Identifier;
struct Assignment;
struct FunctionDefinition;
struct FunctionCall;
struct Scope;
using Statement = boost::variant<Instruction, Literal, Label, Assignment, Identifier, FunctionalAssignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Block>;
struct AsmAnalysisInfo
{
using StackHeightInfo = std::map<void const*, int>;
using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>;
Scopes scopes;
StackHeightInfo stackHeightInfo;
/// Virtual blocks which will be used for scopes for function arguments and return values.
std::map<FunctionDefinition const*, std::shared_ptr<assembly::Block const>> virtualBlocks;
};
}

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