Merge pull request #8825 from ethereum/develop

Merge develop into release.
This commit is contained in:
chriseth 2020-05-04 15:43:01 +02:00 committed by GitHub
commit b8d736ae0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
376 changed files with 8657 additions and 3044 deletions

View File

@ -284,12 +284,18 @@ jobs:
chk_coding_style:
docker:
- image: buildpack-deps:disco
- image: buildpack-deps:focal
steps:
- checkout
- run:
name: Install shellcheck
command: apt -q update && apt install -y shellcheck
- run:
name: Check for C++ coding style
command: ./scripts/check_style.sh
- run:
name: checking shell scripts
command: ./scripts/chk_shellscripts/chk_shellscripts.sh
chk_pylint:
docker:
@ -338,7 +344,7 @@ jobs:
chk_proofs:
docker:
- image: buildpack-deps:disco
- image: buildpack-deps:latest
environment:
TERM: xterm
steps:
@ -347,8 +353,8 @@ jobs:
name: Z3 python deps
command: |
apt-get -qq update
apt-get -qy install python-pip
pip install --user z3-solver
apt-get -qy install python3-pip
pip3 install --user z3-solver
- run: *run_proofs
chk_docs_pragma_min_version:
@ -661,7 +667,7 @@ jobs:
t_ems_solcjs:
docker:
- image: ethereum/solidity-buildpack-deps:ubuntu1904
- image: buildpack-deps:latest
environment:
TERM: xterm
steps:

View File

@ -52,8 +52,8 @@ then
rm -rf z3-4.8.7-x64-osx-10.14.6
# evmone
wget https://github.com/ethereum/evmone/releases/download/v0.3.0/evmone-0.3.0-darwin-x86_64.tar.gz
tar xzpf evmone-0.3.0-darwin-x86_64.tar.gz -C /usr/local
rm -f evmone-0.3.0-darwin-x86_64.tar.gz
wget https://github.com/ethereum/evmone/releases/download/v0.4.0/evmone-0.4.0-darwin-x86_64.tar.gz
tar xzpf evmone-0.4.0-darwin-x86_64.tar.gz -C /usr/local
rm -f evmone-0.4.0-darwin-x86_64.tar.gz
fi

View File

@ -10,7 +10,7 @@ include(EthPolicy)
eth_policy()
# project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.6.6")
set(PROJECT_VERSION "0.6.7")
# OSX target needed in order to support std::visit
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14")
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)

View File

@ -1,3 +1,29 @@
### 0.6.7 (2020-05-04)
Language Features:
* Add support for EIP 165 interface identifiers with `type(I).interfaceId`.
* Allow virtual modifiers inside abstract contracts to have empty body.
Compiler Features:
* Optimizer: Simplify repeated AND and OR operations.
* Standard Json Input: Support the prefix ``file://`` in the field ``urls``.
* Add option to specify optimization steps to be performed by Yul optimizer with `--yul-optimizations` in the commandline interface or `optimizer.details.yulDetails.optimizerSteps` in standard-json.
Bugfixes:
* SMTChecker: Fix internal error when fixed points are used.
* SMTChecker: Fix internal error when using array slices.
* Type Checker: Disallow ``virtual`` and ``override`` for constructors.
* Type Checker: Fix several internal errors by performing size and recursiveness checks of types before the full type checking.
* Type Checker: Fix internal error when assigning to empty tuples.
* Type Checker: Fix internal error when applying unary operators to tuples with empty components.
* Type Checker: Perform recursiveness check on structs declared at the file level.
Build System:
* soltest.sh: ``SOLIDITY_BUILD_DIR`` is no longer relative to ``REPO_ROOT`` to allow for build directories outside of the source tree.
### 0.6.6 (2020-04-09)
Important Bugfixes:

View File

@ -68,7 +68,7 @@ structDefinition
'{' ( variableDeclaration ';' (variableDeclaration ';')* )? '}' ;
modifierDefinition
: 'modifier' identifier parameterList? ( VirtualKeyword | overrideSpecifier )* block ;
: 'modifier' identifier parameterList? ( VirtualKeyword | overrideSpecifier )* ( ';' | block ) ;
functionDefinition
: functionDescriptor parameterList modifierList returnParameters? ( ';' | block ) ;

622
docs/_static/css/dark.css vendored Normal file
View File

@ -0,0 +1,622 @@
/* links */
a,
a:visited {
color: #aaddff;
}
/* code directives */
.method dt,
.class dt,
.data dt,
.attribute dt,
.function dt,
.classmethod dt,
.exception dt,
.descclassname,
.descname {
background-color: #2d2d2d !important;
}
.rst-content dl:not(.docutils) dt {
color: #aaddff;
background-color: #2d2d2d;
border-top: solid 3px #525252;
border-left: solid 3px #525252;
}
em.property {
color: #888888;
}
/* tables */
.rst-content table.docutils thead {
color: #ddd;
}
.rst-content table.docutils td {
border: 0px;
}
.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td {
background-color: #5a5a5a;
}
/* inlined code highlights */
.xref,
.py-meth,
.rst-content a code {
color: #aaddff !important;
font-weight: normal !important;
}
.rst-content code {
color: #eee !important;
font-weight: normal !important;
}
code.literal {
background-color: #2d2d2d !important;
border: 1px solid #6d6d6d !important;
}
code.docutils.literal.notranslate {
color: #ddd;
}
/* notes, warnings, hints */
.hint .admonition-title {
background: #2aa87c !important;
}
.warning .admonition-title {
background: #cc4444 !important;
}
.admonition-title {
background: #3a7ca8 !important;
}
.admonition,
.note {
background-color: #2d2d2d !important;
}
/* table of contents */
.wy-nav-content-wrap {
background-color: rgba(0, 0, 0, 0.6) !important;
}
.sidebar {
background-color: #191919 !important;
}
.sidebar-title {
background-color: #2b2b2b !important;
}
.wy-menu-vertical a {
color: #ddd;
}
.wy-menu-vertical code.docutils.literal.notranslate {
color: #404040;
background: none !important;
border: none !important;
}
.wy-nav-content {
background: #3c3c3c;
color: #dddddd;
}
.wy-menu-vertical li.on a,
.wy-menu-vertical li.current>a {
background: #a3a3a3;
border-bottom: 0px !important;
border-top: 0px !important;
}
.wy-menu-vertical li.current {
background: #b3b3b3;
}
.toc-backref {
color: grey !important;
}
.highlight .hll {
background-color: #49483e
}
.highlight {
background: #222;
color: #f8f8f2
}
.highlight .c {
color: #888
}
/* Comment */
.highlight .err {
color: #960050;
background-color: #1e0010
}
/* Error */
.highlight .k {
color: #66d9ef
}
/* Keyword */
.highlight .l {
color: #ae81ff
}
/* Literal */
.highlight .n {
color: #f8f8f2
}
/* Name */
.highlight .o {
color: #f92672
}
/* Operator */
.highlight .p {
color: #f8f8f2
}
/* Punctuation */
.highlight .ch {
color: #888
}
/* Comment.Hashbang */
.highlight .cm {
color: #888
}
/* Comment.Multiline */
.highlight .cp {
color: #888
}
/* Comment.Preproc */
.highlight .cpf {
color: #888
}
/* Comment.PreprocFile */
.highlight .c1 {
color: #888
}
/* Comment.Single */
.highlight .cs {
color: #888
}
/* Comment.Special */
.highlight .gd {
color: #f92672
}
/* Generic.Deleted */
.highlight .ge {
font-style: italic
}
/* Generic.Emph */
.highlight .gi {
color: #a6e22e
}
/* Generic.Inserted */
.highlight .gs {
font-weight: bold
}
/* Generic.Strong */
.highlight .gu {
color: #888
}
/* Generic.Subheading */
.highlight .kc {
color: #66d9ef
}
/* Keyword.Constant */
.highlight .kd {
color: #66d9ef
}
/* Keyword.Declaration */
.highlight .kn {
color: #f92672
}
/* Keyword.Namespace */
.highlight .kp {
color: #66d9ef
}
/* Keyword.Pseudo */
.highlight .kr {
color: #66d9ef
}
/* Keyword.Reserved */
.highlight .kt {
color: #66d9ef
}
/* Keyword.Type */
.highlight .ld {
color: #e6db74
}
/* Literal.Date */
.highlight .m {
color: #ae81ff
}
/* Literal.Number */
.highlight .s {
color: #e6db74
}
/* Literal.String */
.highlight .na {
color: #a6e22e
}
/* Name.Attribute */
.highlight .nb {
color: #f8f8f2
}
/* Name.Builtin */
.highlight .nc {
color: #a6e22e
}
/* Name.Class */
.highlight .no {
color: #66d9ef
}
/* Name.Constant */
.highlight .nd {
color: #a6e22e
}
/* Name.Decorator */
.highlight .ni {
color: #f8f8f2
}
/* Name.Entity */
.highlight .ne {
color: #a6e22e
}
/* Name.Exception */
.highlight .nf {
color: #a6e22e
}
/* Name.Function */
.highlight .nl {
color: #f8f8f2
}
/* Name.Label */
.highlight .nn {
color: #f8f8f2
}
/* Name.Namespace */
.highlight .nx {
color: #a6e22e
}
/* Name.Other */
.highlight .py {
color: #f8f8f2
}
/* Name.Property */
.highlight .nt {
color: #f92672
}
/* Name.Tag */
.highlight .nv {
color: #f8f8f2
}
/* Name.Variable */
.highlight .ow {
color: #f92672
}
/* Operator.Word */
.highlight .w {
color: #f8f8f2
}
/* Text.Whitespace */
.highlight .mb {
color: #ae81ff
}
/* Literal.Number.Bin */
.highlight .mf {
color: #ae81ff
}
/* Literal.Number.Float */
.highlight .mh {
color: #ae81ff
}
/* Literal.Number.Hex */
.highlight .mi {
color: #ae81ff
}
/* Literal.Number.Integer */
.highlight .mo {
color: #ae81ff
}
/* Literal.Number.Oct */
.highlight .sa {
color: #e6db74
}
/* Literal.String.Affix */
.highlight .sb {
color: #e6db74
}
/* Literal.String.Backtick */
.highlight .sc {
color: #e6db74
}
/* Literal.String.Char */
.highlight .dl {
color: #e6db74
}
/* Literal.String.Delimiter */
.highlight .sd {
color: #e6db74
}
/* Literal.String.Doc */
.highlight .s2 {
color: #e6db74
}
/* Literal.String.Double */
.highlight .se {
color: #ae81ff
}
/* Literal.String.Escape */
.highlight .sh {
color: #e6db74
}
/* Literal.String.Heredoc */
.highlight .si {
color: #e6db74
}
/* Literal.String.Interpol */
.highlight .sx {
color: #e6db74
}
/* Literal.String.Other */
.highlight .sr {
color: #e6db74
}
/* Literal.String.Regex */
.highlight .s1 {
color: #e6db74
}
/* Literal.String.Single */
.highlight .ss {
color: #e6db74
}
/* Literal.String.Symbol */
.highlight .bp {
color: #f8f8f2
}
/* Name.Builtin.Pseudo */
.highlight .fm {
color: #a6e22e
}
/* Name.Function.Magic */
.highlight .vc {
color: #f8f8f2
}
/* Name.Variable.Class */
.highlight .vg {
color: #f8f8f2
}
/* Name.Variable.Global */
.highlight .vi {
color: #f8f8f2
}
/* Name.Variable.Instance */
.highlight .vm {
color: #f8f8f2
}
/* Name.Variable.Magic */
.highlight .il {
color: #ae81ff
}
/* Literal.Number.Integer.Long */

77
docs/_static/css/toggle.css vendored Normal file
View File

@ -0,0 +1,77 @@
input[type=checkbox] {
visibility: hidden;
height: 0;
width: 0;
margin: 0;
}
.rst-versions .rst-current-version {
padding: 10px;
display: flex;
justify-content: space-between;
}
.rst-versions .rst-current-version .fa-book,
.rst-versions .rst-current-version .fa-v,
.rst-versions .rst-current-version .fa-caret-down {
height: 24px;
line-height: 24px;
vertical-align: middle;
}
.rst-versions .rst-current-version .fa-element {
width: 80px;
text-align: center;
}
.rst-versions .rst-current-version .fa-book {
text-align: left;
}
.rst-versions .rst-current-version .fa-v {
color: #27AE60;
text-align: right;
}
label {
margin: 0 auto;
display: inline-block;
justify-content: center;
align-items: right;
border-radius: 100px;
position: relative;
cursor: pointer;
text-indent: -9999px;
width: 50px;
height: 21px;
background: #000;
}
label:after {
border-radius: 50%;
position: absolute;
content: '';
background: #fff;
width: 15px;
height: 15px;
top: 3px;
left: 3px;
transition: ease-in-out 200ms;
}
input:checked+label {
background: #3a7ca8;
}
input:checked+label:after {
left: calc(100% - 5px);
transform: translateX(-100%);
}
html.transition,
html.transition *,
html.transition *:before,
html.transition *:after {
transition: ease-in-out 200ms !important;
transition-delay: 0 !important;
}

38
docs/_static/js/toggle.js vendored Normal file
View File

@ -0,0 +1,38 @@
document.addEventListener('DOMContentLoaded', function() {
function toggleCssMode(isDay) {
var mode = (isDay ? "Day" : "Night");
localStorage.setItem("css-mode", mode);
var daysheet = $('link[href="_static/pygments.css"]')[0].sheet;
daysheet.disabled = !isDay;
var nightsheet = $('link[href="_static/css/dark.css"]')[0];
if (!isDay && nightsheet === undefined) {
var element = document.createElement("link");
element.setAttribute("rel", "stylesheet");
element.setAttribute("type", "text/css");
element.setAttribute("href", "_static/css/dark.css");
document.getElementsByTagName("head")[0].appendChild(element);
return;
}
if (nightsheet !== undefined) {
nightsheet.sheet.disabled = isDay;
}
}
var initial = localStorage.getItem("css-mode") != "Night";
var checkbox = document.querySelector('input[name=mode]');
toggleCssMode(initial);
checkbox.checked = initial;
checkbox.addEventListener('change', function() {
document.documentElement.classList.add('transition');
window.setTimeout(() => {
document.documentElement.classList.remove('transition');
}, 1000)
toggleCssMode(this.checked);
})
});

36
docs/_templates/versions.html vendored Normal file
View File

@ -0,0 +1,36 @@
{# Add rst-badge after rst-versions for small badge style. #}
<div class="rst-versions" data-toggle="rst-versions" role="note" aria-label="versions">
<span class="rst-current-version" data-toggle="rst-current-version">
<span class="fa fa-book fa-element"> RTD </span>
<span class="fa fa-element">
<input class="container_toggle" type="checkbox" id="switch" name="mode">
<label for="switch"></label>
</span>
<span class="fa fa-v fa-element"> v: {{ current_version }} <span class="fa fa-caret-down"></span></span>
</span>
<div class="rst-other-versions">
<dl>
<dt>{{ _('Versions') }}</dt> {% for slug, url in versions %}
<dd><a href="{{ url }}">{{ slug }}</a></dd>
{% endfor %}
</dl>
<dl>
<dt>{{ _('Downloads') }}</dt> {% for type, url in downloads %}
<dd><a href="{{ url }}">{{ type }}</a></dd>
{% endfor %}
</dl>
<dl>
{# Translators: The phrase "Read the Docs" is not translated #}
<dt>{{ _('On Read the Docs') }}</dt>
<dd>
<a href="//{{ PRODUCTION_DOMAIN }}/projects/{{ slug }}/?fromdocs={{ slug }}">{{ _('Project Home') }}</a>
</dd>
<dd>
<a href="//{{ PRODUCTION_DOMAIN }}/builds/{{ slug }}/?fromdocs={{ slug }}">{{ _('Builds') }}</a>
</dd>
</dl>
</div>
</div>

View File

@ -202,7 +202,7 @@ on the type of ``X`` being
- ``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 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-order (left) side with ``0xff`` for negative ``X`` and with zero bytes for positive ``X`` such that the length is 32 bytes.
- ``int<M>``: ``enc(X)`` is the big-endian two's complement encoding of ``X``, padded on the higher-order (left) side with ``0xff`` bytes for negative ``X`` and with zero-bytes for non-negative ``X`` such that the length is 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 ``fixed128x18`` case
@ -507,13 +507,17 @@ A function description is a JSON object with the fields:
- ``outputs``: an array of objects similar to ``inputs``.
- ``stateMutability``: a string with one of the following values: ``pure`` (:ref:`specified to not read
blockchain state <pure-functions>`), ``view`` (:ref:`specified to not modify the blockchain
state <view-functions>`), ``nonpayable`` (function does not accept Ether) and ``payable`` (function accepts Ether).
state <view-functions>`), ``nonpayable`` (function does not accept Ether - the default) and ``payable`` (function accepts Ether).
Constructor and fallback function never have ``name`` or ``outputs``. Fallback function doesn't have ``inputs`` either.
.. note::
Sending non-zero Ether to non-payable function will revert the transaction.
.. note::
The state mutability ``nonpayable`` is reflected in Solidity by not specifying
a state mutability modifier at all.
An event description is a JSON object with fairly similar fields:
- ``type``: always ``"event"``

View File

@ -11,6 +11,7 @@
"name": "MemoryArrayCreationOverflow",
"summary": "The creation of very large memory arrays can result in overlapping memory regions and thus memory corruption.",
"description": "No runtime overflow checks were performed for the length of memory arrays during creation. In cases for which the memory size of an array in bytes, i.e. the array length times 32, is larger than 2^256-1, the memory allocation will overflow, potentially resulting in overlapping memory areas. The length of the array is still stored correctly, so copying or iterating over such an array will result in out-of-gas.",
"link": "https://solidity.ethereum.org/2020/04/06/memory-creation-overflow-bug/",
"introduced": "0.2.0",
"fixed": "0.6.5",
"severity": "low"
@ -73,6 +74,7 @@
"name": "SignedArrayStorageCopy",
"summary": "Assigning an array of signed integers to a storage array of different type can lead to data corruption in that array.",
"description": "In two's complement, negative integers have their higher order bits set. In order to fit into a shared storage slot, these have to be set to zero. When a conversion is done at the same time, the bits to set to zero were incorrectly determined from the source and not the target type. This means that such copy operations can lead to incorrect values being stored.",
"link": "https://blog.ethereum.org/2019/06/25/solidity-storage-array-bugs/",
"introduced": "0.4.7",
"fixed": "0.5.10",
"severity": "low/medium"
@ -81,6 +83,7 @@
"name": "ABIEncoderV2StorageArrayWithMultiSlotElement",
"summary": "Storage arrays containing structs or other statically-sized arrays are not read properly when directly encoded in external function calls or in abi.encode*.",
"description": "When storage arrays whose elements occupy more than a single storage slot are directly encoded in external function calls or using abi.encode*, their elements are read in an overlapping manner, i.e. the element pointer is not properly advanced between reads. This is not a problem when the storage data is first copied to a memory variable or if the storage array only contains value types or dynamically-sized arrays.",
"link": "https://blog.ethereum.org/2019/06/25/solidity-storage-array-bugs/",
"introduced": "0.4.16",
"fixed": "0.5.10",
"severity": "low",

View File

@ -1085,5 +1085,9 @@
"0.6.6": {
"bugs": [],
"released": "2020-04-09"
},
"0.6.7": {
"bugs": [],
"released": "2020-05-04"
}
}

191
docs/cheatsheet.rst Normal file
View File

@ -0,0 +1,191 @@
**********
Cheatsheet
**********
.. index:: precedence
.. _order:
Order of Precedence of Operators
================================
The following is the order of precedence for operators, listed in order of evaluation.
+------------+-------------------------------------+--------------------------------------------+
| Precedence | Description | Operator |
+============+=====================================+============================================+
| *1* | Postfix increment and decrement | ``++``, ``--`` |
+ +-------------------------------------+--------------------------------------------+
| | New expression | ``new <typename>`` |
+ +-------------------------------------+--------------------------------------------+
| | Array subscripting | ``<array>[<index>]`` |
+ +-------------------------------------+--------------------------------------------+
| | Member access | ``<object>.<member>`` |
+ +-------------------------------------+--------------------------------------------+
| | Function-like call | ``<func>(<args...>)`` |
+ +-------------------------------------+--------------------------------------------+
| | Parentheses | ``(<statement>)`` |
+------------+-------------------------------------+--------------------------------------------+
| *2* | Prefix increment and decrement | ``++``, ``--`` |
+ +-------------------------------------+--------------------------------------------+
| | Unary minus | ``-`` |
+ +-------------------------------------+--------------------------------------------+
| | Unary operations | ``delete`` |
+ +-------------------------------------+--------------------------------------------+
| | Logical NOT | ``!`` |
+ +-------------------------------------+--------------------------------------------+
| | Bitwise NOT | ``~`` |
+------------+-------------------------------------+--------------------------------------------+
| *3* | Exponentiation | ``**`` |
+------------+-------------------------------------+--------------------------------------------+
| *4* | Multiplication, division and modulo | ``*``, ``/``, ``%`` |
+------------+-------------------------------------+--------------------------------------------+
| *5* | Addition and subtraction | ``+``, ``-`` |
+------------+-------------------------------------+--------------------------------------------+
| *6* | Bitwise shift operators | ``<<``, ``>>`` |
+------------+-------------------------------------+--------------------------------------------+
| *7* | Bitwise AND | ``&`` |
+------------+-------------------------------------+--------------------------------------------+
| *8* | Bitwise XOR | ``^`` |
+------------+-------------------------------------+--------------------------------------------+
| *9* | Bitwise OR | ``|`` |
+------------+-------------------------------------+--------------------------------------------+
| *10* | Inequality operators | ``<``, ``>``, ``<=``, ``>=`` |
+------------+-------------------------------------+--------------------------------------------+
| *11* | Equality operators | ``==``, ``!=`` |
+------------+-------------------------------------+--------------------------------------------+
| *12* | Logical AND | ``&&`` |
+------------+-------------------------------------+--------------------------------------------+
| *13* | Logical OR | ``||`` |
+------------+-------------------------------------+--------------------------------------------+
| *14* | Ternary operator | ``<conditional> ? <if-true> : <if-false>`` |
+ +-------------------------------------+--------------------------------------------+
| | Assignment operators | ``=``, ``|=``, ``^=``, ``&=``, ``<<=``, |
| | | ``>>=``, ``+=``, ``-=``, ``*=``, ``/=``, |
| | | ``%=`` |
+------------+-------------------------------------+--------------------------------------------+
| *15* | Comma operator | ``,`` |
+------------+-------------------------------------+--------------------------------------------+
.. index:: assert, block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, revert, require, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send
Global Variables
================
- ``abi.decode(bytes memory encodedData, (...)) returns (...)``: :ref:`ABI <ABI>`-decodes
the provided data. The types are given in parentheses as second argument.
Example: ``(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))``
- ``abi.encode(...) returns (bytes memory)``: :ref:`ABI <ABI>`-encodes the given arguments
- ``abi.encodePacked(...) returns (bytes memory)``: Performs :ref:`packed encoding <abi_packed_mode>` of
the given arguments. Note that this encoding can be ambiguous!
- ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)``: :ref:`ABI <ABI>`-encodes
the given arguments starting from the second and prepends the given four-byte selector
- ``abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)``: Equivalent
to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)```
- ``block.coinbase`` (``address payable``): current block miner's address
- ``block.difficulty`` (``uint``): current block difficulty
- ``block.gaslimit`` (``uint``): current block gaslimit
- ``block.number`` (``uint``): current block number
- ``block.timestamp`` (``uint``): current block timestamp
- ``gasleft() returns (uint256)``: remaining gas
- ``msg.data`` (``bytes``): complete calldata
- ``msg.sender`` (``address payable``): sender of the message (current call)
- ``msg.value`` (``uint``): number of wei sent with the message
- ``now`` (``uint``): current block timestamp (alias for ``block.timestamp``)
- ``tx.gasprice`` (``uint``): gas price of the transaction
- ``tx.origin`` (``address payable``): 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 or error in external component)
- ``require(bool condition, string memory message)``: abort execution and revert state changes if
condition is ``false`` (use for malformed input or error in external component). Also provide error message.
- ``revert()``: abort execution and revert state changes
- ``revert(string memory message)``: abort execution and revert state changes providing an explanatory string
- ``blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent blocks
- ``keccak256(bytes memory) returns (bytes32)``: compute the Keccak-256 hash of the input
- ``sha256(bytes memory) returns (bytes32)``: compute the SHA-256 hash of the input
- ``ripemd160(bytes memory) returns (bytes20)``: compute the RIPEMD-160 hash of the input
- ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover address associated with
the public key from elliptic curve signature, return zero on error
- ``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``. Assert that ``k != 0`` starting from version 0.5.0.
- ``mulmod(uint x, uint y, uint k) returns (uint)``: compute ``(x * y) % k`` where the multiplication is performed
with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0.
- ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` or ``address payable``
- ``super``: the contract one level higher in the inheritance hierarchy
- ``selfdestruct(address payable recipient)``: destroy the current contract, sending its funds to the given address
- ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei
- ``<address payable>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`,
returns ``false`` on failure
- ``<address payable>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure
- ``type(C).name`` (``string``): the name of the contract
- ``type(C).creationCode`` (``bytes memory``): creation bytecode of the given contract, see :ref:`Type Information<meta-type>`.
- ``type(C).runtimeCode`` (``bytes memory``): runtime bytecode of the given contract, see :ref:`Type Information<meta-type>`.
- ``type(I).interfaceId`` (``bytes4``): value containing the EIP-165 interface identifier of the given interface, see :ref:`Type Information<meta-type>`.
.. note::
Do not rely on ``block.timestamp``, ``now`` and ``blockhash`` as a source of randomness,
unless you know what you are doing.
Both the timestamp and the block hash can be influenced by miners to some degree.
Bad actors in the mining community can for example run a casino payout function on a chosen hash
and just retry a different hash if they did not receive any money.
The current block timestamp must be strictly larger than the timestamp of the last block,
but the only guarantee is that it will be somewhere between the timestamps of two
consecutive blocks in the canonical chain.
.. note::
The block hashes are not available for all blocks for scalability reasons.
You can only access the hashes of the most recent 256 blocks, all other
values will be zero.
.. note::
In version 0.5.0, the following aliases were removed: ``suicide`` as alias for ``selfdestruct``,
``msg.gas`` as alias for ``gasleft``, ``block.blockhash`` as alias for ``blockhash`` and
``sha3`` as alias for ``keccak256``.
.. index:: visibility, public, private, external, internal
Function Visibility Specifiers
==============================
::
function myFunction() <visibility specifier> returns (bool) {
return true;
}
- ``public``: visible externally and internally (creates a :ref:`getter function<getter-functions>` for storage/state variables)
- ``private``: only visible in the current contract
- ``external``: only visible externally (only for functions) - i.e. can only be message-called (via ``this.func``)
- ``internal``: only visible internally
.. index:: modifiers, pure, view, payable, constant, anonymous, indexed
Modifiers
=========
- ``pure`` for functions: Disallows modification or access of state.
- ``view`` for functions: Disallows modification of state.
- ``payable`` for functions: Allows them to receive Ether together with a call.
- ``constant`` for state variables: Disallows assignment (except initialisation), does not occupy storage slot.
- ``immutable`` for state variables: Allows exactly one assignment at construction time and is constant afterwards. Is stored in code.
- ``anonymous`` for events: Does not store event signature as topic.
- ``indexed`` for event parameters: Stores the parameter as topic.
- ``virtual`` for functions and modifiers: Allows the function's or modifier's
behaviour to be changed in derived contracts.
- ``override``: States that this function, modifier or public state variable changes
the behaviour of a function or modifier in a base contract.
Reserved Keywords
=================
These keywords are reserved in Solidity. They might become part of the syntax in the future:
``after``, ``alias``, ``apply``, ``auto``, ``case``, ``copyof``, ``default``,
``define``, ``final``, ``immutable``, ``implements``, ``in``, ``inline``, ``let``, ``macro``, ``match``,
``mutable``, ``null``, ``of``, ``partial``, ``promise``, ``reference``, ``relocatable``,
``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``typedef``, ``typeof``,
``unchecked``.

View File

@ -17,7 +17,7 @@ import sys
import os
import re
from pygments_lexer_solidity import SolidityLexer
from pygments_lexer_solidity import SolidityLexer, YulLexer
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
@ -27,6 +27,7 @@ def setup(sphinx):
thisdir = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, thisdir + '/utils')
sphinx.add_lexer('Solidity', SolidityLexer())
sphinx.add_lexer('Yul', YulLexer())
sphinx.add_stylesheet('css/custom.css')
@ -146,10 +147,14 @@ html_theme = 'sphinx_rtd_theme'
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_css_files = ["css/toggle.css"]
html_js_files = ["js/toggle.js"]
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
html_extra_path = ["_static/css"]
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.

View File

@ -52,3 +52,8 @@ facilitating patterns like the `Template method <https://en.wikipedia.org/wiki/T
Abstract contracts are useful in the same way that defining methods
in an interface is useful. It is a way for the designer of the
abstract contract to say "any child of mine must implement this method".
.. note::
Abstract contracts cannot override an implemented virtual function with an
unimplemented one.

View File

@ -195,7 +195,7 @@ In addition to the list of state modifying statements explained above, the follo
}
}
Pure functions are able to use the `revert()` and `require()` functions to revert
Pure functions are able to use the ``revert()`` and ``require()`` functions to revert
potential state changes when an :ref:`error occurs <assert-and-require>`.
Reverting a state change is not considered a "state modification", as only changes to the
@ -235,9 +235,9 @@ A contract can have at most one ``receive`` function, declared using
``receive() external payable { ... }``
(without the ``function`` keyword).
This function cannot have arguments, cannot return anything and must have
``external`` visibility and ``payable`` state mutability. It is executed on a
``external`` visibility and ``payable`` state mutability. It is executed on a
call to the contract with empty calldata. This is the function that is executed
on plain Ether transfers (e.g. via `.send()` or `.transfer()`). If no such
on plain Ether transfers (e.g. via ``.send()`` or ``.transfer()``). If no such
function exists, but a payable :ref:`fallback function <fallback-function>`
exists, the fallback function will be called on a plain Ether transfer. If
neither a receive Ether nor a payable fallback function is present, the
@ -245,7 +245,7 @@ contract cannot receive Ether through regular transactions and throws an
exception.
In the worst case, the fallback function can only rely on 2300 gas being
available (for example when `send` or `transfer` is used), leaving little
available (for example when ``send`` or ``transfer`` is used), leaving little
room to perform other operations except basic logging. The following operations
will consume more gas than the 2300 gas stipend:
@ -265,7 +265,7 @@ will consume more gas than the 2300 gas stipend:
.. warning::
A contract without a receive Ether function can receive Ether as a
recipient of a `coinbase transaction` (aka `miner block reward`)
recipient of a *coinbase transaction* (aka *miner block reward*)
or as a destination of a ``selfdestruct``.
A contract cannot react to such Ether transfers and thus also

View File

@ -16,7 +16,7 @@ Functions have to be specified as being ``external``,
``public``, ``internal`` or ``private``.
For state variables, ``external`` is not possible.
``external``:
``external``
External functions are part of the contract interface,
which means they can be called from other contracts and
via transactions. An external function ``f`` cannot be called
@ -25,18 +25,18 @@ For state variables, ``external`` is not possible.
they receive large arrays of data, because the data
is not copied from calldata to memory.
``public``:
``public``
Public functions are part of the contract interface
and can be either called internally or via
messages. For public state variables, an automatic getter
function (see below) is generated.
``internal``:
``internal``
Those functions and state variables can only be
accessed internally (i.e. from within the current contract
or contracts deriving from it), without using ``this``.
``private``:
``private``
Private functions and state variables are only
visible for the contract they are defined in and not in
derived contracts.

View File

@ -81,26 +81,55 @@ Thank you for your help!
Running the compiler tests
==========================
The ``./scripts/tests.sh`` script executes most Solidity tests automatically,
but for quicker feedback, you might want to run specific tests.
Prerequisites
-------------
Some tests require the `evmone <https://github.com/ethereum/evmone/releases>`_
library, others require `libz3 <https://github.com/Z3Prover/z3>`_. The test script
tries to discover the location of the ``evmone`` library, which can be located
in the current directory, installed on the system level, or the ``deps`` folder
in the project top level. The required file is called ``libevmone.so`` on Linux
systems, ``evmone.dll`` on Windows systems and ``libevmone.dylib`` on macOS.
Running the tests
-----------------
Solidity includes different types of tests, most of them bundled into the
`Boost C++ Test Framework <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/index.html>`_ application ``soltest``.
Running ``build/test/soltest`` or its wrapper ``scripts/soltest.sh`` is sufficient for most changes.
Some tests require the ``evmone`` library, others require ``libz3``.
The ``./scripts/tests.sh`` script executes most Solidity tests automatically,
including those bundled into the `Boost C++ Test Framework <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/index.html>`_ application ``soltest`` (or its wrapper ``scripts/soltest.sh``),
as well as command line tests and compilation tests.
The test system will automatically try to discover the location of the ``evmone`` library
The test system automatically tries try to discover the location of the ``evmone`` library
starting from the current directory. The required file is called ``libevmone.so`` on Linux systems,
``evmone.dll`` on Windows systems and ``libevmone.dylib`` on MacOS. If it is not found, the relevant tests
are skipped. To run all tests, download the library from
`Github <https://github.com/ethereum/evmone/releases/tag/v0.3.0>`_
and either place it in the project root path or inside the ``deps`` folder.
``evmone.dll`` on Windows systems and ``libevmone.dylib`` on macOS. If it is not found, tests that
use it are skipped. These tests are ``libsolididty/semanticTests``, ``libsolidity/GasCosts``,
``libsolidity/SolidityEndToEndTest``, part of the soltest suite. To run all tests, download the library from
`GitHub <https://github.com/ethereum/evmone/releases/tag/v0.4.1>`_
and place it in the project root path or inside the ``deps`` folder.
If you do not have libz3 installed on your system, you should disable the SMT tests:
``./scripts/soltest.sh --no-smt``.
If the ``libz3`` library is not installed on your system, you should disable the
SMT tests by exporting ``SMT_FLAGS=--no-smt`` before running ``./scripts/tests.sh`` or
running ``./scripts/soltest.sh --no-smt``.
These tests are ``libsolidity/smtCheckerTests`` and ``libsolidity/smtCheckerTestsJSON``.
.. note ::
To get a list of all unit tests run by Soltest, run ``./build/test/soltest --list_content=HRF``.
For quicker results you can run a subset of, or specific tests.
To run a subset of tests, you can use filters:
``./scripts/soltest.sh -t TestSuite/TestName``,
where ``TestName`` can be a wildcard ``*``.
Or, for example, to run all the tests for the yul disambiguator:
``./scripts/soltest.sh -t "yulOptimizerTests/disambiguator/*" --no-smt``.
``./build/test/soltest --help`` has extensive help on all of the options available.
See especially:
- `show_progress (-p) <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/boost_test/utf_reference/rt_param_reference/show_progress.html>`_ to show test completion,
@ -109,20 +138,10 @@ See especially:
.. note ::
Those working in a Windows environment wanting to run the above basic sets without libz3 in Git Bash, you would have to do: ``./build/test/Release/soltest.exe -- --no-smt``.
Those working in a Windows environment wanting to run the above basic sets
without libz3. Using Git Bash, you use: ``./build/test/Release/soltest.exe -- --no-smt``.
If you are running this in plain Command Prompt, use ``.\build\test\Release\soltest.exe -- --no-smt``.
To run a subset of tests, you can use filters:
``./scripts/soltest.sh -t TestSuite/TestName``,
where ``TestName`` can be a wildcard ``*``.
For example, here is an example test you might run;
``./scripts/soltest.sh -t "yulOptimizerTests/disambiguator/*" --no-smt``.
This will test all the tests for the disambiguator.
To get a list of all tests, use
``./build/test/soltest --list_content=HRF``.
If you want to debug using GDB, make sure you build differently than the "usual".
For example, you could run the following command in your ``build`` folder:
::
@ -130,14 +149,11 @@ For example, you could run the following command in your ``build`` folder:
cmake -DCMAKE_BUILD_TYPE=Debug ..
make
This will create symbols such that when you debug a test using the ``--debug`` flag, you will have access to functions and variables in which you can break or print with.
The script ``./scripts/tests.sh`` also runs commandline tests and compilation tests
in addition to those found in ``soltest``.
The CI runs additional tests (including ``solc-js`` and testing third party Solidity frameworks) that require compiling the Emscripten target.
This creates symbols so that when you debug a test using the ``--debug`` flag,
you have access to functions and variables in which you can break or print with.
The CI runs additional tests (including ``solc-js`` and testing third party Solidity
frameworks) that require compiling the Emscripten target.
Writing and running syntax tests
--------------------------------
@ -340,6 +356,11 @@ by as many concatenations of its contents as there were sets of variables suppli
each time replacing any ``<inner>`` items by their respective value. Top-level variables can also be used
inside such areas.
There are also conditionals of the form ``<?name>...<!name>...</name>``, where template replacements
continue recursively either in the first or the second segment depending on the value of the boolean
parameter ``name``. If ``<?+name>...<!+name>...</+name>`` is used, then the check is whether
the string parameter ``name`` is non-empty.
.. _documentation-style:
Documentation Style Guide

View File

@ -454,7 +454,7 @@ The recipient should verify each message using the following process:
We'll use the `ethereumjs-util <https://github.com/ethereumjs/ethereumjs-util>`_
library to write this verification. The final step can be done a number of ways,
and we use JavaScript. The following code borrows the `constructMessage` function from the signing **JavaScript code** above:
and we use JavaScript. The following code borrows the ``constructMessage`` function from the signing **JavaScript code** above:
::

6
docs/grammar.rst Normal file
View File

@ -0,0 +1,6 @@
****************
Language Grammar
****************
.. literalinclude:: Solidity.g4
:language: antlr

View File

@ -36,7 +36,7 @@ If you are new to the concept of smart contracts we recommend you start with
:ref:`an example smart contract <simple-smart-contract>` written
in Solidity. When you are ready for more detail, we recommend you read the
:doc:`"Solidity by Example" <solidity-by-example>` and
:doc:`"Solidity in Depth" <solidity-in-depth>` sections to learn the core concepts of the language.
"Language Description" sections to learn the core concepts of the language.
For further reading, try :ref:`the basics of blockchains <blockchain-basics>`
and details of the :ref:`Ethereum Virtual Machine <the-ethereum-virtual-machine>`.
@ -88,17 +88,49 @@ Contents
.. toctree::
:maxdepth: 2
:caption: Basics
introduction-to-smart-contracts.rst
installing-solidity.rst
solidity-by-example.rst
solidity-in-depth.rst
.. toctree::
:maxdepth: 2
:caption: Language Description
layout-of-source-files.rst
structure-of-a-contract.rst
types.rst
units-and-global-variables.rst
control-structures.rst
contracts.rst
assembly.rst
cheatsheet.rst
grammar.rst
.. toctree::
:maxdepth: 2
:caption: Internals
internals/layout_in_storage.rst
internals/layout_in_memory.rst
internals/layout_in_calldata.rst
internals/variable_cleanup.rst
internals/source_mappings.rst
internals/optimiser.rst
metadata.rst
abi-spec.rst
.. toctree::
:maxdepth: 2
:caption: Additional Material
050-breaking-changes.rst
060-breaking-changes.rst
natspec-format.rst
security-considerations.rst
resources.rst
using-the-compiler.rst
metadata.rst
abi-spec.rst
yul.rst
style-guide.rst
common-patterns.rst

View File

@ -35,11 +35,11 @@ or if you require more compilation options.
npm / Node.js
=============
Use `npm` for a convenient and portable way to install `solcjs`, a Solidity compiler. The
Use ``npm`` for a convenient and portable way to install ``solcjs``, a Solidity compiler. The
`solcjs` program has fewer features than the ways to access the compiler described
further down this page. The
:ref:`commandline-compiler` documentation assumes you are using
the full-featured compiler, `solc`. The usage of `solcjs` is documented inside its own
the full-featured compiler, ``solc``. The usage of ``solcjs`` is documented inside its own
`repository <https://github.com/ethereum/solc-js>`_.
Note: The solc-js project is derived from the C++
@ -53,10 +53,10 @@ Please refer to the solc-js repository for instructions.
.. note::
The commandline executable is named `solcjs`.
The commandline executable is named ``solcjs``.
The comandline options of `solcjs` are not compatible with `solc` and tools (such as `geth`)
expecting the behaviour of `solc` will not work with `solcjs`.
The comandline options of ``solcjs`` are not compatible with ``solc`` and tools (such as ``geth``)
expecting the behaviour of ``solc`` will not work with ``solcjs``.
Docker
======

View File

@ -0,0 +1,13 @@
*******************
Layout of Call Data
*******************
The input data for a function call is assumed to be in the format defined by the :ref:`ABI
specification <ABI>`. Among others, the ABI specification requires arguments to be padded to multiples of 32
bytes. The internal function calls use a different convention.
Arguments for the constructor of a contract are directly appended at the end of the
contract's code, also in ABI encoding. The constructor will access them through a hard-coded offset, and
not by using the ``codesize`` opcode, since this of course changes when appending
data to the code.

View File

@ -0,0 +1,39 @@
.. index: memory layout
****************
Layout in Memory
****************
Solidity reserves four 32-byte slots, with specific byte ranges (inclusive of endpoints) being used as follows:
- ``0x00`` - ``0x3f`` (64 bytes): scratch space for hashing methods
- ``0x40`` - ``0x5f`` (32 bytes): currently allocated memory size (aka. free memory pointer)
- ``0x60`` - ``0x7f`` (32 bytes): zero slot
Scratch space can be used between statements (i.e. within inline assembly). The zero slot
is used as initial value for dynamic memory arrays and should never be written to
(the free memory pointer points to ``0x80`` initially).
Solidity always places new objects at the free memory pointer and
memory is never freed (this might change in the future).
Elements in memory arrays in Solidity always occupy multiples of 32 bytes (this
is even true for ``byte[]``, but not for ``bytes`` and ``string``).
Multi-dimensional memory arrays are pointers to memory arrays. The length of a
dynamic array is stored at the first slot of the array and followed by the array
elements.
.. warning::
There are some operations in Solidity that need a temporary memory area
larger than 64 bytes and therefore will not fit into the scratch space.
They will be placed where the free memory points to, but given their
short lifetime, the pointer is not updated. The memory may or may not
be zeroed out. Because of this, one should not expect the free memory
to point to zeroed out memory.
While it may seem like a good idea to use ``msize`` to arrive at a
definitely zeroed out memory area, using such a pointer non-temporarily
without updating the free memory pointer can have unexpected results.
.. index: calldata layout

View File

@ -0,0 +1,359 @@
.. index:: storage, state variable, mapping
************************************
Layout of State Variables in Storage
************************************
.. _storage-inplace-encoding:
Statically-sized variables (everything except mapping and dynamically-sized
array types) are laid out contiguously in storage starting from position ``0``.
Multiple, contiguous items that need less than 32 bytes are packed into a single
storage slot if possible, according to the following rules:
- The first item in a storage slot is stored lower-order aligned.
- Elementary types use only as many bytes as are necessary to store them.
- If an elementary type does not fit the remaining part of a storage slot, it is moved to the next storage slot.
- Structs and array data always start a new slot and occupy whole slots
(but items inside a struct or array are packed tightly according to these rules).
For contracts that use inheritance, the ordering of state variables is determined by the
C3-linearized order of contracts starting with the most base-ward contract. If allowed
by the above rules, state variables from different contracts do share the same storage slot.
The elements of structs and arrays are stored after each other, just as if they were given explicitly.
.. warning::
When using elements that are smaller than 32 bytes, your contract's gas usage may be higher.
This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller
than that, the EVM must use more operations in order to reduce the size of the element from 32
bytes to the desired size.
It is only beneficial to use reduced-size arguments if you are dealing with storage values
because the compiler will pack multiple elements into one storage slot, and thus, combine
multiple reads or writes into a single operation. When dealing with function arguments or memory
values, there is no inherent benefit because the compiler does not pack these values.
Finally, in order to allow the EVM to optimize for this, ensure that you try to order your
storage variables and ``struct`` members such that they can be packed tightly. For example,
declaring your storage variables in the order of ``uint128, uint128, uint256`` instead of
``uint128, uint256, uint128``, as the former will only take up two slots of storage whereas the
latter will take up three.
.. note::
The layout of state variables in storage is considered to be part of the external interface
of Solidity due to the fact that storage pointers can be passed to libraries. This means that
any change to the rules outlined in this section is considered a breaking change
of the language and due to its critical nature should be considered very carefully before
being executed.
Mappings and Dynamic Arrays
===========================
.. _storage-hashed-encoding:
Due to their unpredictable size, mapping and dynamically-sized array types use a Keccak-256 hash
computation to find the starting position of the value or the array data.
These starting positions are always full stack slots.
The mapping or the dynamic array itself occupies a slot in storage at some position ``p``
according to the above rule (or by recursively applying this rule for
mappings of mappings or arrays of arrays). For dynamic arrays,
this slot stores the number of elements in the array (byte arrays and
strings are an exception, see :ref:`below <bytes-and-string>`).
For mappings, the slot is unused (but it is needed so that two equal mappings after each other will use a different
hash distribution). Array data is located at ``keccak256(p)`` and the value corresponding to a mapping key
``k`` is located at ``keccak256(k . p)`` where ``.`` is concatenation. If the value is again a
non-elementary type, the positions are found by adding an offset of ``keccak256(k . p)``.
So for the following contract snippet
the position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1``::
pragma solidity >=0.4.0 <0.7.0;
contract C {
struct S { uint a; uint b; }
uint x;
mapping(uint => mapping(uint => S)) data;
}
.. _bytes-and-string:
``bytes`` and ``string``
------------------------
``bytes`` and ``string`` are encoded identically. For short byte arrays, they store their data in the same
slot where the length is also stored. In particular: if the data is at most ``31`` bytes long, it is stored
in the higher-order bytes (left aligned) and the lowest-order byte stores ``length * 2``.
For byte arrays that store data which is ``32`` or more bytes long, the main slot stores ``length * 2 + 1`` and the data is
stored as usual in ``keccak256(slot)``. This means that you can distinguish a short array from a long array
by checking if the lowest bit is set: short (not set) and long (set).
.. note::
Handling invalidly encoded slots is currently not supported but may be added in the future.
JSON Output
===========
.. _storage-layout-top-level:
The storage layout of a contract can be requested via
the :ref:`standard JSON interface <compiler-api>`. The output is a JSON object containing two keys,
``storage`` and ``types``. The ``storage`` object is an array where each
element has the following form:
.. code::
{
"astId": 2,
"contract": "fileA:A",
"label": "x",
"offset": 0,
"slot": "0",
"type": "t_uint256"
}
The example above is the storage layout of ``contract A { uint x; }`` from source unit ``fileA``
and
- ``astId`` is the id of the AST node of the state variable's declaration
- ``contract`` is the name of the contract including its path as prefix
- ``label`` is the name of the state variable
- ``offset`` is the offset in bytes within the storage slot according to the encoding
- ``slot`` is the storage slot where the state variable resides or starts. This
number may be very large and therefore its JSON value is represented as a
string.
- ``type`` is an identifier used as key to the variable's type information (described in the following)
The given ``type``, in this case ``t_uint256`` represents an element in
``types``, which has the form:
.. code::
{
"encoding": "inplace",
"label": "uint256",
"numberOfBytes": "32",
}
where
- ``encoding`` how the data is encoded in storage, where the possible values are:
- ``inplace``: data is laid out contiguously in storage (see :ref:`above <storage-inplace-encoding>`).
- ``mapping``: Keccak-256 hash-based method (see :ref:`above <storage-hashed-encoding>`).
- ``dynamic_array``: Keccak-256 hash-based method (see :ref:`above <storage-hashed-encoding>`).
- ``bytes``: single slot or Keccak-256 hash-based depending on the data size (see :ref:`above <bytes-and-string>`).
- ``label`` is the canonical type name.
- ``numberOfBytes`` is the number of used bytes (as a decimal string).
Note that if ``numberOfBytes > 32`` this means that more than one slot is used.
Some types have extra information besides the four above. Mappings contain
its ``key`` and ``value`` types (again referencing an entry in this mapping
of types), arrays have its ``base`` type, and structs list their ``members`` in
the same format as the top-level ``storage`` (see :ref:`above
<storage-layout-top-level>`).
.. note ::
The JSON output format of a contract's storage layout is still considered experimental
and is subject to change in non-breaking releases of Solidity.
The following example shows a contract and its storage layout, containing
value and reference types, types that are encoded packed, and nested types.
.. code::
pragma solidity >=0.4.0 <0.7.0;
contract A {
struct S {
uint128 a;
uint128 b;
uint[2] staticArray;
uint[] dynArray;
}
uint x;
uint y;
S s;
address addr;
mapping (uint => mapping (address => bool)) map;
uint[] array;
string s1;
bytes b1;
}
.. code::
"storageLayout": {
"storage": [
{
"astId": 14,
"contract": "fileA:A",
"label": "x",
"offset": 0,
"slot": "0",
"type": "t_uint256"
},
{
"astId": 16,
"contract": "fileA:A",
"label": "y",
"offset": 0,
"slot": "1",
"type": "t_uint256"
},
{
"astId": 18,
"contract": "fileA:A",
"label": "s",
"offset": 0,
"slot": "2",
"type": "t_struct(S)12_storage"
},
{
"astId": 20,
"contract": "fileA:A",
"label": "addr",
"offset": 0,
"slot": "6",
"type": "t_address"
},
{
"astId": 26,
"contract": "fileA:A",
"label": "map",
"offset": 0,
"slot": "7",
"type": "t_mapping(t_uint256,t_mapping(t_address,t_bool))"
},
{
"astId": 29,
"contract": "fileA:A",
"label": "array",
"offset": 0,
"slot": "8",
"type": "t_array(t_uint256)dyn_storage"
},
{
"astId": 31,
"contract": "fileA:A",
"label": "s1",
"offset": 0,
"slot": "9",
"type": "t_string_storage"
},
{
"astId": 33,
"contract": "fileA:A",
"label": "b1",
"offset": 0,
"slot": "10",
"type": "t_bytes_storage"
}
],
"types": {
"t_address": {
"encoding": "inplace",
"label": "address",
"numberOfBytes": "20"
},
"t_array(t_uint256)2_storage": {
"base": "t_uint256",
"encoding": "inplace",
"label": "uint256[2]",
"numberOfBytes": "64"
},
"t_array(t_uint256)dyn_storage": {
"base": "t_uint256",
"encoding": "dynamic_array",
"label": "uint256[]",
"numberOfBytes": "32"
},
"t_bool": {
"encoding": "inplace",
"label": "bool",
"numberOfBytes": "1"
},
"t_bytes_storage": {
"encoding": "bytes",
"label": "bytes",
"numberOfBytes": "32"
},
"t_mapping(t_address,t_bool)": {
"encoding": "mapping",
"key": "t_address",
"label": "mapping(address => bool)",
"numberOfBytes": "32",
"value": "t_bool"
},
"t_mapping(t_uint256,t_mapping(t_address,t_bool))": {
"encoding": "mapping",
"key": "t_uint256",
"label": "mapping(uint256 => mapping(address => bool))",
"numberOfBytes": "32",
"value": "t_mapping(t_address,t_bool)"
},
"t_string_storage": {
"encoding": "bytes",
"label": "string",
"numberOfBytes": "32"
},
"t_struct(S)12_storage": {
"encoding": "inplace",
"label": "struct A.S",
"members": [
{
"astId": 2,
"contract": "fileA:A",
"label": "a",
"offset": 0,
"slot": "0",
"type": "t_uint128"
},
{
"astId": 4,
"contract": "fileA:A",
"label": "b",
"offset": 16,
"slot": "0",
"type": "t_uint128"
},
{
"astId": 8,
"contract": "fileA:A",
"label": "staticArray",
"offset": 0,
"slot": "1",
"type": "t_array(t_uint256)2_storage"
},
{
"astId": 11,
"contract": "fileA:A",
"label": "dynArray",
"offset": 0,
"slot": "3",
"type": "t_array(t_uint256)dyn_storage"
}
],
"numberOfBytes": "128"
},
"t_uint128": {
"encoding": "inplace",
"label": "uint128",
"numberOfBytes": "16"
},
"t_uint256": {
"encoding": "inplace",
"label": "uint256",
"numberOfBytes": "32"
}
}
}

View File

@ -0,0 +1,71 @@
.. index:: optimizer, common subexpression elimination, constant propagation
*************
The Optimiser
*************
This section discusses the optimiser that was first added to Solidity,
which operates on opcode streams. For information on the new Yul-based optimiser,
please see the `readme on github <https://github.com/ethereum/solidity/blob/develop/libyul/optimiser/README.md>`_.
The Solidity optimiser operates on assembly. It splits the sequence of instructions into basic blocks
at ``JUMPs`` and ``JUMPDESTs``. Inside these blocks, the optimiser
analyses the instructions and records every modification to the stack,
memory, or storage as an expression which consists of an instruction and
a list of arguments which are pointers to other expressions. The optimiser
uses a component called "CommonSubexpressionEliminator" that amongst other
tasks, finds expressions that are always equal (on every input) and combines
them into an expression class. The optimiser first tries to find each new
expression in a list of already known expressions. If this does not work,
it simplifies the expression according to rules like
``constant + constant = sum_of_constants`` or ``X * 1 = X``. Since this is
a recursive process, we can also apply the latter rule if the second factor
is a more complex expression where we know that it always evaluates to one.
Modifications to storage and memory locations have to erase knowledge about
storage and memory locations which are not known to be different. If we first
write to location x and then to location y and both are input variables, the
second could overwrite the first, so we do not know what is stored at x after
we wrote to y. If simplification of the expression x - y evaluates to a
non-zero constant, we know that we can keep our knowledge about what is stored at x.
After this process, we know which expressions have to be on the stack at
the end, and have a list of modifications to memory and storage. This information
is stored together with the basic blocks and is used to link them. Furthermore,
knowledge about the stack, storage and memory configuration is forwarded to
the next block(s). If we know the targets of all ``JUMP`` and ``JUMPI`` instructions,
we can build a complete control flow graph of the program. If there is only
one target we do not know (this can happen as in principle, jump targets can
be computed from inputs), we have to erase all knowledge about the input state
of a block as it can be the target of the unknown ``JUMP``. If the optimiser
finds a ``JUMPI`` whose condition evaluates to a constant, it transforms it
to an unconditional jump.
As the last step, the code in each block is re-generated. The optimiser creates
a dependency graph from the expressions on the stack at the end of the block,
and it drops every operation that is not part of this graph. It generates code
that applies the modifications to memory and storage in the order they were
made in the original code (dropping modifications which were found not to be
needed). Finally, it generates all values that are required to be on the
stack in the correct place.
These steps are applied to each basic block and the newly generated code
is used as replacement if it is smaller. If a basic block is split at a
``JUMPI`` and during the analysis, the condition evaluates to a constant,
the ``JUMPI`` is replaced depending on the value of the constant. Thus code like
::
uint x = 7;
data[7] = 9;
if (data[x] != x + 2)
return 2;
else
return 1;
still simplifies to code which you can compile even though the instructions contained
a jump in the beginning of the process:
::
data[7] = 9;
return 1;

View File

@ -0,0 +1,62 @@
.. index:: source mappings
***************
Source Mappings
***************
As part of the AST output, the compiler provides the range of the source
code that is represented by the respective node in the AST. This can be
used for various purposes ranging from static analysis tools that report
errors based on the AST and debugging tools that highlight local variables
and their uses.
Furthermore, the compiler can also generate a mapping from the bytecode
to the range in the source code that generated the instruction. This is again
important for static analysis tools that operate on bytecode level and
for displaying the current position in the source code inside a debugger
or for breakpoint handling. This mapping also contains other information,
like the jump type and the modifier depth (see below).
Both kinds of source mappings use integer identifiers to refer to source files.
The identifier of a source file is stored in
``output['sources'][sourceName]['id']`` where ``output`` is the output of the
standard-json compiler interface parsed as JSON.
.. note ::
In the case of instructions that are not associated with any particular source file,
the source mapping assigns an integer identifier of ``-1``. This may happen for
bytecode sections stemming from compiler-generated inline assembly statements.
The source mappings inside the AST use the following
notation:
``s:l:f``
Where ``s`` is the byte-offset to the start of the range in the source file,
``l`` is the length of the source range in bytes and ``f`` is the source
index mentioned above.
The encoding in the source mapping for the bytecode is more complicated:
It is a list of ``s:l:f:j:m`` separated by ``;``. Each of these
elements corresponds to an instruction, i.e. you cannot use the byte offset
but have to use the instruction offset (push instructions are longer than a single byte).
The fields ``s``, ``l`` and ``f`` are as above. ``j`` can be either
``i``, ``o`` or ``-`` signifying whether a jump instruction goes into a
function, returns from a function or is a regular jump as part of e.g. a loop.
The last field, ``m``, is an integer that denotes the "modifier depth". This depth
is increased whenever the placeholder statement (``_``) is entered in a modifier
and decreased when it is left again. This allows debuggers to track tricky cases
like the same modifier being used twice or multiple placeholder statements being
used in a single modifier.
In order to compress these source mappings especially for bytecode, the
following rules are used:
- If a field is empty, the value of the preceding element is used.
- If a ``:`` is missing, all following fields are considered empty.
This means the following source mappings represent the same information:
``1:2:1;1:9:1;2:1:2;2:1:2;2:1:2``
``1:2:1;:9;2:1:2;;``

View File

@ -0,0 +1,47 @@
.. index: variable cleanup
*********************
Cleaning Up Variables
*********************
When a value is shorter than 256 bit, in some cases the remaining bits
must be cleaned.
The Solidity compiler is designed to clean such remaining bits before any operations
that might be adversely affected by the potential garbage in the remaining bits.
For example, before writing a value to memory, the remaining bits need
to be cleared because the memory contents can be used for computing
hashes or sent as the data of a message call. Similarly, before
storing a value in the storage, the remaining bits need to be cleaned
because otherwise the garbled value can be observed.
On the other hand, we do not clean the bits if the immediately
following operation is not affected. For instance, since any non-zero
value is considered ``true`` by ``JUMPI`` instruction, we do not clean
the boolean values before they are used as the condition for
``JUMPI``.
In addition to the design principle above, the Solidity compiler
cleans input data when it is loaded onto the stack.
Different types have different rules for cleaning up invalid values:
+---------------+---------------+-------------------+
|Type |Valid Values |Invalid Values Mean|
+===============+===============+===================+
|enum of n |0 until n - 1 |exception |
|members | | |
+---------------+---------------+-------------------+
|bool |0 or 1 |1 |
+---------------+---------------+-------------------+
|signed integers|sign-extended |currently silently |
| |word |wraps; in the |
| | |future exceptions |
| | |will be thrown |
| | | |
| | | |
+---------------+---------------+-------------------+
|unsigned |higher bits |currently silently |
|integers |zeroed |wraps; in the |
| | |future exceptions |
| | |will be thrown |
+---------------+---------------+-------------------+

View File

@ -323,7 +323,7 @@ Every account has a persistent key-value store mapping 256-bit words to 256-bit
words called **storage**.
Furthermore, every account has a **balance** in
Ether (in "Wei" to be exact, `1 ether` is `10**18 wei`) which can be modified by sending transactions that
Ether (in "Wei" to be exact, ``1 ether`` is ``10**18 wei``) which can be modified by sending transactions that
include Ether.
.. index:: ! transaction
@ -520,9 +520,9 @@ idea, but it is potentially dangerous, as if someone sends Ether to removed
contracts, the Ether is forever lost.
.. warning::
Even if a contract is removed by "selfdestruct", it is still part of the
Even if a contract is removed by ``selfdestruct``, it is still part of the
history of the blockchain and probably retained by most Ethereum nodes.
So using "selfdestruct" is not the same as deleting data from a hard disk.
So using ``selfdestruct`` is not the same as deleting data from a hard disk.
.. note::
Even if a contract's code does not contain a call to ``selfdestruct``,

View File

@ -1,814 +0,0 @@
#############
Miscellaneous
#############
.. index:: storage, state variable, mapping
************************************
Layout of State Variables in Storage
************************************
.. _storage-inplace-encoding:
Statically-sized variables (everything except mapping and dynamically-sized
array types) are laid out contiguously in storage starting from position ``0``.
Multiple, contiguous items that need less than 32 bytes are packed into a single
storage slot if possible, according to the following rules:
- The first item in a storage slot is stored lower-order aligned.
- Elementary types use only as many bytes as are necessary to store them.
- If an elementary type does not fit the remaining part of a storage slot, it is moved to the next storage slot.
- Structs and array data always start a new slot and occupy whole slots
(but items inside a struct or array are packed tightly according to these rules).
For contracts that use inheritance, the ordering of state variables is determined by the
C3-linearized order of contracts starting with the most base-ward contract. If allowed
by the above rules, state variables from different contracts do share the same storage slot.
The elements of structs and arrays are stored after each other, just as if they were given explicitly.
.. warning::
When using elements that are smaller than 32 bytes, your contract's gas usage may be higher.
This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller
than that, the EVM must use more operations in order to reduce the size of the element from 32
bytes to the desired size.
It is only beneficial to use reduced-size arguments if you are dealing with storage values
because the compiler will pack multiple elements into one storage slot, and thus, combine
multiple reads or writes into a single operation. When dealing with function arguments or memory
values, there is no inherent benefit because the compiler does not pack these values.
Finally, in order to allow the EVM to optimize for this, ensure that you try to order your
storage variables and ``struct`` members such that they can be packed tightly. For example,
declaring your storage variables in the order of ``uint128, uint128, uint256`` instead of
``uint128, uint256, uint128``, as the former will only take up two slots of storage whereas the
latter will take up three.
.. note::
The layout of state variables in storage is considered to be part of the external interface
of Solidity due to the fact that storage pointers can be passed to libraries. This means that
any change to the rules outlined in this section is considered a breaking change
of the language and due to its critical nature should be considered very carefully before
being executed.
Mappings and Dynamic Arrays
===========================
.. _storage-hashed-encoding:
Due to their unpredictable size, mapping and dynamically-sized array types use a Keccak-256 hash
computation to find the starting position of the value or the array data.
These starting positions are always full stack slots.
The mapping or the dynamic array itself occupies a slot in storage at some position ``p``
according to the above rule (or by recursively applying this rule for
mappings of mappings or arrays of arrays). For dynamic arrays,
this slot stores the number of elements in the array (byte arrays and
strings are an exception, see :ref:`below <bytes-and-string>`).
For mappings, the slot is unused (but it is needed so that two equal mappings after each other will use a different
hash distribution). Array data is located at ``keccak256(p)`` and the value corresponding to a mapping key
``k`` is located at ``keccak256(k . p)`` where ``.`` is concatenation. If the value is again a
non-elementary type, the positions are found by adding an offset of ``keccak256(k . p)``.
So for the following contract snippet
the position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1``::
pragma solidity >=0.4.0 <0.7.0;
contract C {
struct S { uint a; uint b; }
uint x;
mapping(uint => mapping(uint => S)) data;
}
.. _bytes-and-string:
``bytes`` and ``string``
------------------------
``bytes`` and ``string`` are encoded identically. For short byte arrays, they store their data in the same
slot where the length is also stored. In particular: if the data is at most ``31`` bytes long, it is stored
in the higher-order bytes (left aligned) and the lowest-order byte stores ``length * 2``.
For byte arrays that store data which is ``32`` or more bytes long, the main slot stores ``length * 2 + 1`` and the data is
stored as usual in ``keccak256(slot)``. This means that you can distinguish a short array from a long array
by checking if the lowest bit is set: short (not set) and long (set).
.. note::
Handling invalidly encoded slots is currently not supported but may be added in the future.
JSON Output
===========
.. _storage-layout-top-level:
The storage layout of a contract can be requested via
the :ref:`standard JSON interface <compiler-api>`. The output is a JSON object containing two keys,
``storage`` and ``types``. The ``storage`` object is an array where each
element has the following form:
.. code::
{
"astId": 2,
"contract": "fileA:A",
"label": "x",
"offset": 0,
"slot": "0",
"type": "t_uint256"
}
The example above is the storage layout of ``contract A { uint x; }`` from source unit ``fileA``
and
- ``astId`` is the id of the AST node of the state variable's declaration
- ``contract`` is the name of the contract including its path as prefix
- ``label`` is the name of the state variable
- ``offset`` is the offset in bytes within the storage slot according to the encoding
- ``slot`` is the storage slot where the state variable resides or starts. This
number may be very large and therefore its JSON value is represented as a
string.
- ``type`` is an identifier used as key to the variable's type information (described in the following)
The given ``type``, in this case ``t_uint256`` represents an element in
``types``, which has the form:
.. code::
{
"encoding": "inplace",
"label": "uint256",
"numberOfBytes": "32",
}
where
- ``encoding`` how the data is encoded in storage, where the possible values are:
- ``inplace``: data is laid out contiguously in storage (see :ref:`above <storage-inplace-encoding>`).
- ``mapping``: Keccak-256 hash-based method (see :ref:`above <storage-hashed-encoding>`).
- ``dynamic_array``: Keccak-256 hash-based method (see :ref:`above <storage-hashed-encoding>`).
- ``bytes``: single slot or Keccak-256 hash-based depending on the data size (see :ref:`above <bytes-and-string>`).
- ``label`` is the canonical type name.
- ``numberOfBytes`` is the number of used bytes (as a decimal string).
Note that if ``numberOfBytes > 32`` this means that more than one slot is used.
Some types have extra information besides the four above. Mappings contain
its ``key`` and ``value`` types (again referencing an entry in this mapping
of types), arrays have its ``base`` type, and structs list their ``members`` in
the same format as the top-level ``storage`` (see :ref:`above
<storage-layout-top-level>`).
.. note ::
The JSON output format of a contract's storage layout is still considered experimental
and is subject to change in non-breaking releases of Solidity.
The following example shows a contract and its storage layout, containing
value and reference types, types that are encoded packed, and nested types.
.. code::
pragma solidity >=0.4.0 <0.7.0;
contract A {
struct S {
uint128 a;
uint128 b;
uint[2] staticArray;
uint[] dynArray;
}
uint x;
uint y;
S s;
address addr;
mapping (uint => mapping (address => bool)) map;
uint[] array;
string s1;
bytes b1;
}
.. code::
"storageLayout": {
"storage": [
{
"astId": 14,
"contract": "fileA:A",
"label": "x",
"offset": 0,
"slot": "0",
"type": "t_uint256"
},
{
"astId": 16,
"contract": "fileA:A",
"label": "y",
"offset": 0,
"slot": "1",
"type": "t_uint256"
},
{
"astId": 18,
"contract": "fileA:A",
"label": "s",
"offset": 0,
"slot": "2",
"type": "t_struct(S)12_storage"
},
{
"astId": 20,
"contract": "fileA:A",
"label": "addr",
"offset": 0,
"slot": "6",
"type": "t_address"
},
{
"astId": 26,
"contract": "fileA:A",
"label": "map",
"offset": 0,
"slot": "7",
"type": "t_mapping(t_uint256,t_mapping(t_address,t_bool))"
},
{
"astId": 29,
"contract": "fileA:A",
"label": "array",
"offset": 0,
"slot": "8",
"type": "t_array(t_uint256)dyn_storage"
},
{
"astId": 31,
"contract": "fileA:A",
"label": "s1",
"offset": 0,
"slot": "9",
"type": "t_string_storage"
},
{
"astId": 33,
"contract": "fileA:A",
"label": "b1",
"offset": 0,
"slot": "10",
"type": "t_bytes_storage"
}
],
"types": {
"t_address": {
"encoding": "inplace",
"label": "address",
"numberOfBytes": "20"
},
"t_array(t_uint256)2_storage": {
"base": "t_uint256",
"encoding": "inplace",
"label": "uint256[2]",
"numberOfBytes": "64"
},
"t_array(t_uint256)dyn_storage": {
"base": "t_uint256",
"encoding": "dynamic_array",
"label": "uint256[]",
"numberOfBytes": "32"
},
"t_bool": {
"encoding": "inplace",
"label": "bool",
"numberOfBytes": "1"
},
"t_bytes_storage": {
"encoding": "bytes",
"label": "bytes",
"numberOfBytes": "32"
},
"t_mapping(t_address,t_bool)": {
"encoding": "mapping",
"key": "t_address",
"label": "mapping(address => bool)",
"numberOfBytes": "32",
"value": "t_bool"
},
"t_mapping(t_uint256,t_mapping(t_address,t_bool))": {
"encoding": "mapping",
"key": "t_uint256",
"label": "mapping(uint256 => mapping(address => bool))",
"numberOfBytes": "32",
"value": "t_mapping(t_address,t_bool)"
},
"t_string_storage": {
"encoding": "bytes",
"label": "string",
"numberOfBytes": "32"
},
"t_struct(S)12_storage": {
"encoding": "inplace",
"label": "struct A.S",
"members": [
{
"astId": 2,
"contract": "fileA:A",
"label": "a",
"offset": 0,
"slot": "0",
"type": "t_uint128"
},
{
"astId": 4,
"contract": "fileA:A",
"label": "b",
"offset": 16,
"slot": "0",
"type": "t_uint128"
},
{
"astId": 8,
"contract": "fileA:A",
"label": "staticArray",
"offset": 0,
"slot": "1",
"type": "t_array(t_uint256)2_storage"
},
{
"astId": 11,
"contract": "fileA:A",
"label": "dynArray",
"offset": 0,
"slot": "3",
"type": "t_array(t_uint256)dyn_storage"
}
],
"numberOfBytes": "128"
},
"t_uint128": {
"encoding": "inplace",
"label": "uint128",
"numberOfBytes": "16"
},
"t_uint256": {
"encoding": "inplace",
"label": "uint256",
"numberOfBytes": "32"
}
}
}
.. index: memory layout
****************
Layout in Memory
****************
Solidity reserves four 32-byte slots, with specific byte ranges (inclusive of endpoints) being used as follows:
- ``0x00`` - ``0x3f`` (64 bytes): scratch space for hashing methods
- ``0x40`` - ``0x5f`` (32 bytes): currently allocated memory size (aka. free memory pointer)
- ``0x60`` - ``0x7f`` (32 bytes): zero slot
Scratch space can be used between statements (i.e. within inline assembly). The zero slot
is used as initial value for dynamic memory arrays and should never be written to
(the free memory pointer points to ``0x80`` initially).
Solidity always places new objects at the free memory pointer and
memory is never freed (this might change in the future).
Elements in memory arrays in Solidity always occupy multiples of 32 bytes (this
is even true for ``byte[]``, but not for ``bytes`` and ``string``).
Multi-dimensional memory arrays are pointers to memory arrays. The length of a
dynamic array is stored at the first slot of the array and followed by the array
elements.
.. warning::
There are some operations in Solidity that need a temporary memory area
larger than 64 bytes and therefore will not fit into the scratch space.
They will be placed where the free memory points to, but given their
short lifetime, the pointer is not updated. The memory may or may not
be zeroed out. Because of this, one should not expect the free memory
to point to zeroed out memory.
While it may seem like a good idea to use ``msize`` to arrive at a
definitely zeroed out memory area, using such a pointer non-temporarily
without updating the free memory pointer can have unexpected results.
.. index: calldata layout
*******************
Layout of Call Data
*******************
The input data for a function call is assumed to be in the format defined by the :ref:`ABI
specification <ABI>`. Among others, the ABI specification requires arguments to be padded to multiples of 32
bytes. The internal function calls use a different convention.
Arguments for the constructor of a contract are directly appended at the end of the
contract's code, also in ABI encoding. The constructor will access them through a hard-coded offset, and
not by using the ``codesize`` opcode, since this of course changes when appending
data to the code.
.. index: variable cleanup
*********************************
Internals - Cleaning Up Variables
*********************************
When a value is shorter than 256 bit, in some cases the remaining bits
must be cleaned.
The Solidity compiler is designed to clean such remaining bits before any operations
that might be adversely affected by the potential garbage in the remaining bits.
For example, before writing a value to memory, the remaining bits need
to be cleared because the memory contents can be used for computing
hashes or sent as the data of a message call. Similarly, before
storing a value in the storage, the remaining bits need to be cleaned
because otherwise the garbled value can be observed.
On the other hand, we do not clean the bits if the immediately
following operation is not affected. For instance, since any non-zero
value is considered ``true`` by ``JUMPI`` instruction, we do not clean
the boolean values before they are used as the condition for
``JUMPI``.
In addition to the design principle above, the Solidity compiler
cleans input data when it is loaded onto the stack.
Different types have different rules for cleaning up invalid values:
+---------------+---------------+-------------------+
|Type |Valid Values |Invalid Values Mean|
+===============+===============+===================+
|enum of n |0 until n - 1 |exception |
|members | | |
+---------------+---------------+-------------------+
|bool |0 or 1 |1 |
+---------------+---------------+-------------------+
|signed integers|sign-extended |currently silently |
| |word |wraps; in the |
| | |future exceptions |
| | |will be thrown |
| | | |
| | | |
+---------------+---------------+-------------------+
|unsigned |higher bits |currently silently |
|integers |zeroed |wraps; in the |
| | |future exceptions |
| | |will be thrown |
+---------------+---------------+-------------------+
.. index:: optimizer, common subexpression elimination, constant propagation
*************************
Internals - The Optimiser
*************************
This section discusses the optimiser that was first added to Solidity,
which operates on opcode streams. For information on the new Yul-based optimiser,
please see the `readme on github <https://github.com/ethereum/solidity/blob/develop/libyul/optimiser/README.md>`_.
The Solidity optimiser operates on assembly. It splits the sequence of instructions into basic blocks
at ``JUMPs`` and ``JUMPDESTs``. Inside these blocks, the optimiser
analyses the instructions and records every modification to the stack,
memory, or storage as an expression which consists of an instruction and
a list of arguments which are pointers to other expressions. The optimiser
uses a component called "CommonSubexpressionEliminator" that amongst other
tasks, finds expressions that are always equal (on every input) and combines
them into an expression class. The optimiser first tries to find each new
expression in a list of already known expressions. If this does not work,
it simplifies the expression according to rules like
``constant + constant = sum_of_constants`` or ``X * 1 = X``. Since this is
a recursive process, we can also apply the latter rule if the second factor
is a more complex expression where we know that it always evaluates to one.
Modifications to storage and memory locations have to erase knowledge about
storage and memory locations which are not known to be different. If we first
write to location x and then to location y and both are input variables, the
second could overwrite the first, so we do not know what is stored at x after
we wrote to y. If simplification of the expression x - y evaluates to a
non-zero constant, we know that we can keep our knowledge about what is stored at x.
After this process, we know which expressions have to be on the stack at
the end, and have a list of modifications to memory and storage. This information
is stored together with the basic blocks and is used to link them. Furthermore,
knowledge about the stack, storage and memory configuration is forwarded to
the next block(s). If we know the targets of all ``JUMP`` and ``JUMPI`` instructions,
we can build a complete control flow graph of the program. If there is only
one target we do not know (this can happen as in principle, jump targets can
be computed from inputs), we have to erase all knowledge about the input state
of a block as it can be the target of the unknown ``JUMP``. If the optimiser
finds a ``JUMPI`` whose condition evaluates to a constant, it transforms it
to an unconditional jump.
As the last step, the code in each block is re-generated. The optimiser creates
a dependency graph from the expressions on the stack at the end of the block,
and it drops every operation that is not part of this graph. It generates code
that applies the modifications to memory and storage in the order they were
made in the original code (dropping modifications which were found not to be
needed). Finally, it generates all values that are required to be on the
stack in the correct place.
These steps are applied to each basic block and the newly generated code
is used as replacement if it is smaller. If a basic block is split at a
``JUMPI`` and during the analysis, the condition evaluates to a constant,
the ``JUMPI`` is replaced depending on the value of the constant. Thus code like
::
uint x = 7;
data[7] = 9;
if (data[x] != x + 2)
return 2;
else
return 1;
still simplifies to code which you can compile even though the instructions contained
a jump in the beginning of the process:
::
data[7] = 9;
return 1;
.. index:: source mappings
***************
Source Mappings
***************
As part of the AST output, the compiler provides the range of the source
code that is represented by the respective node in the AST. This can be
used for various purposes ranging from static analysis tools that report
errors based on the AST and debugging tools that highlight local variables
and their uses.
Furthermore, the compiler can also generate a mapping from the bytecode
to the range in the source code that generated the instruction. This is again
important for static analysis tools that operate on bytecode level and
for displaying the current position in the source code inside a debugger
or for breakpoint handling. This mapping also contains other information,
like the jump type and the modifier depth (see below).
Both kinds of source mappings use integer identifiers to refer to source files.
The identifier of a source file is stored in
``output['sources'][sourceName]['id']`` where ``output`` is the output of the
standard-json compiler interface parsed as JSON.
.. note ::
In the case of instructions that are not associated with any particular source file,
the source mapping assigns an integer identifier of ``-1``. This may happen for
bytecode sections stemming from compiler-generated inline assembly statements.
The source mappings inside the AST use the following
notation:
``s:l:f``
Where ``s`` is the byte-offset to the start of the range in the source file,
``l`` is the length of the source range in bytes and ``f`` is the source
index mentioned above.
The encoding in the source mapping for the bytecode is more complicated:
It is a list of ``s:l:f:j:m`` separated by ``;``. Each of these
elements corresponds to an instruction, i.e. you cannot use the byte offset
but have to use the instruction offset (push instructions are longer than a single byte).
The fields ``s``, ``l`` and ``f`` are as above. ``j`` can be either
``i``, ``o`` or ``-`` signifying whether a jump instruction goes into a
function, returns from a function or is a regular jump as part of e.g. a loop.
The last field, ``m``, is an integer that denotes the "modifier depth". This depth
is increased whenever the placeholder statement (``_``) is entered in a modifier
and decreased when it is left again. This allows debuggers to track tricky cases
like the same modifier being used twice or multiple placeholder statements being
used in a single modifier.
In order to compress these source mappings especially for bytecode, the
following rules are used:
- If a field is empty, the value of the preceding element is used.
- If a ``:`` is missing, all following fields are considered empty.
This means the following source mappings represent the same information:
``1:2:1;1:9:1;2:1:2;2:1:2;2:1:2``
``1:2:1;:9;2:1:2;;``
***************
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 creates :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`.
* Initialize storage structs with a single assignment: ``x = MyStruct({a: 1, b: 2});``
.. note::
If the storage struct has tightly packed properties, initialize it with separate
assignments: ``x.a = 1; x.b = 2;``. In this way it will be easier for the
optimizer to update storage in one go, thus making assignment cheaper.
**********
Cheatsheet
**********
.. index:: precedence
.. _order:
Order of Precedence of Operators
================================
The following is the order of precedence for operators, listed in order of evaluation.
+------------+-------------------------------------+--------------------------------------------+
| Precedence | Description | Operator |
+============+=====================================+============================================+
| *1* | Postfix increment and decrement | ``++``, ``--`` |
+ +-------------------------------------+--------------------------------------------+
| | New expression | ``new <typename>`` |
+ +-------------------------------------+--------------------------------------------+
| | Array subscripting | ``<array>[<index>]`` |
+ +-------------------------------------+--------------------------------------------+
| | Member access | ``<object>.<member>`` |
+ +-------------------------------------+--------------------------------------------+
| | Function-like call | ``<func>(<args...>)`` |
+ +-------------------------------------+--------------------------------------------+
| | Parentheses | ``(<statement>)`` |
+------------+-------------------------------------+--------------------------------------------+
| *2* | Prefix increment and decrement | ``++``, ``--`` |
+ +-------------------------------------+--------------------------------------------+
| | Unary minus | ``-`` |
+ +-------------------------------------+--------------------------------------------+
| | Unary operations | ``delete`` |
+ +-------------------------------------+--------------------------------------------+
| | Logical NOT | ``!`` |
+ +-------------------------------------+--------------------------------------------+
| | Bitwise NOT | ``~`` |
+------------+-------------------------------------+--------------------------------------------+
| *3* | Exponentiation | ``**`` |
+------------+-------------------------------------+--------------------------------------------+
| *4* | Multiplication, division and modulo | ``*``, ``/``, ``%`` |
+------------+-------------------------------------+--------------------------------------------+
| *5* | Addition and subtraction | ``+``, ``-`` |
+------------+-------------------------------------+--------------------------------------------+
| *6* | Bitwise shift operators | ``<<``, ``>>`` |
+------------+-------------------------------------+--------------------------------------------+
| *7* | Bitwise AND | ``&`` |
+------------+-------------------------------------+--------------------------------------------+
| *8* | Bitwise XOR | ``^`` |
+------------+-------------------------------------+--------------------------------------------+
| *9* | Bitwise OR | ``|`` |
+------------+-------------------------------------+--------------------------------------------+
| *10* | Inequality operators | ``<``, ``>``, ``<=``, ``>=`` |
+------------+-------------------------------------+--------------------------------------------+
| *11* | Equality operators | ``==``, ``!=`` |
+------------+-------------------------------------+--------------------------------------------+
| *12* | Logical AND | ``&&`` |
+------------+-------------------------------------+--------------------------------------------+
| *13* | Logical OR | ``||`` |
+------------+-------------------------------------+--------------------------------------------+
| *14* | Ternary operator | ``<conditional> ? <if-true> : <if-false>`` |
+ +-------------------------------------+--------------------------------------------+
| | Assignment operators | ``=``, ``|=``, ``^=``, ``&=``, ``<<=``, |
| | | ``>>=``, ``+=``, ``-=``, ``*=``, ``/=``, |
| | | ``%=`` |
+------------+-------------------------------------+--------------------------------------------+
| *15* | Comma operator | ``,`` |
+------------+-------------------------------------+--------------------------------------------+
.. index:: assert, block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, revert, require, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send
Global Variables
================
- ``abi.decode(bytes memory encodedData, (...)) returns (...)``: :ref:`ABI <ABI>`-decodes
the provided data. The types are given in parentheses as second argument.
Example: ``(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))``
- ``abi.encode(...) returns (bytes memory)``: :ref:`ABI <ABI>`-encodes the given arguments
- ``abi.encodePacked(...) returns (bytes memory)``: Performs :ref:`packed encoding <abi_packed_mode>` of
the given arguments. Note that this encoding can be ambiguous!
- ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)``: :ref:`ABI <ABI>`-encodes
the given arguments starting from the second and prepends the given four-byte selector
- ``abi.encodeWithSignature(string memory signature, ...) returns (bytes memory)``: Equivalent
to ``abi.encodeWithSelector(bytes4(keccak256(bytes(signature)), ...)```
- ``block.coinbase`` (``address payable``): current block miner's address
- ``block.difficulty`` (``uint``): current block difficulty
- ``block.gaslimit`` (``uint``): current block gaslimit
- ``block.number`` (``uint``): current block number
- ``block.timestamp`` (``uint``): current block timestamp
- ``gasleft() returns (uint256)``: remaining gas
- ``msg.data`` (``bytes``): complete calldata
- ``msg.sender`` (``address payable``): sender of the message (current call)
- ``msg.value`` (``uint``): number of wei sent with the message
- ``now`` (``uint``): current block timestamp (alias for ``block.timestamp``)
- ``tx.gasprice`` (``uint``): gas price of the transaction
- ``tx.origin`` (``address payable``): 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 or error in external component)
- ``require(bool condition, string memory message)``: abort execution and revert state changes if
condition is ``false`` (use for malformed input or error in external component). Also provide error message.
- ``revert()``: abort execution and revert state changes
- ``revert(string memory message)``: abort execution and revert state changes providing an explanatory string
- ``blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent blocks
- ``keccak256(bytes memory) returns (bytes32)``: compute the Keccak-256 hash of the input
- ``sha256(bytes memory) returns (bytes32)``: compute the SHA-256 hash of the input
- ``ripemd160(bytes memory) returns (bytes20)``: compute the RIPEMD-160 hash of the input
- ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover address associated with
the public key from elliptic curve signature, return zero on error
- ``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``. Assert that ``k != 0`` starting from version 0.5.0.
- ``mulmod(uint x, uint y, uint k) returns (uint)``: compute ``(x * y) % k`` where the multiplication is performed
with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0.
- ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` or ``address payable``
- ``super``: the contract one level higher in the inheritance hierarchy
- ``selfdestruct(address payable recipient)``: destroy the current contract, sending its funds to the given address
- ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei
- ``<address payable>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`,
returns ``false`` on failure
- ``<address payable>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure
- ``type(C).name`` (``string``): the name of the contract
- ``type(C).creationCode`` (``bytes memory``): creation bytecode of the given contract, see :ref:`Type Information<meta-type>`.
- ``type(C).runtimeCode`` (``bytes memory``): runtime bytecode of the given contract, see :ref:`Type Information<meta-type>`.
.. note::
Do not rely on ``block.timestamp``, ``now`` and ``blockhash`` as a source of randomness,
unless you know what you are doing.
Both the timestamp and the block hash can be influenced by miners to some degree.
Bad actors in the mining community can for example run a casino payout function on a chosen hash
and just retry a different hash if they did not receive any money.
The current block timestamp must be strictly larger than the timestamp of the last block,
but the only guarantee is that it will be somewhere between the timestamps of two
consecutive blocks in the canonical chain.
.. note::
The block hashes are not available for all blocks for scalability reasons.
You can only access the hashes of the most recent 256 blocks, all other
values will be zero.
.. note::
In version 0.5.0, the following aliases were removed: ``suicide`` as alias for ``selfdestruct``,
``msg.gas`` as alias for ``gasleft``, ``block.blockhash`` as alias for ``blockhash`` and
``sha3`` as alias for ``keccak256``.
.. index:: visibility, public, private, external, internal
Function Visibility Specifiers
==============================
::
function myFunction() <visibility specifier> returns (bool) {
return true;
}
- ``public``: visible externally and internally (creates a :ref:`getter function<getter-functions>` for storage/state variables)
- ``private``: only visible in the current contract
- ``external``: only visible externally (only for functions) - i.e. can only be message-called (via ``this.func``)
- ``internal``: only visible internally
.. index:: modifiers, pure, view, payable, constant, anonymous, indexed
Modifiers
=========
- ``pure`` for functions: Disallows modification or access of state.
- ``view`` for functions: Disallows modification of state.
- ``payable`` for functions: Allows them to receive Ether together with a call.
- ``constant`` for state variables: Disallows assignment (except initialisation), does not occupy storage slot.
- ``immutable`` for state variables: Allows exactly one assignment at construction time and is constant afterwards. Is stored in code.
- ``anonymous`` for events: Does not store event signature as topic.
- ``indexed`` for event parameters: Stores the parameter as topic.
- ``virtual`` for functions and modifiers: Allows the function's or modifier's
behaviour to be changed in derived contracts.
- ``override``: States that this function, modifier or public state variable changes
the behaviour of a function or modifier in a base contract.
Reserved Keywords
=================
These keywords are reserved in Solidity. They might become part of the syntax in the future:
``after``, ``alias``, ``apply``, ``auto``, ``case``, ``copyof``, ``default``,
``define``, ``final``, ``immutable``, ``implements``, ``in``, ``inline``, ``let``, ``macro``, ``match``,
``mutable``, ``null``, ``of``, ``partial``, ``promise``, ``reference``, ``relocatable``,
``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``typedef``, ``typeof``,
``unchecked``.
Language Grammar
================
.. literalinclude:: Solidity.g4
:language: antlr

View File

@ -73,8 +73,8 @@ Tags
All tags are optional. The following table explains the purpose of each
NatSpec tag and where it may be used. As a special case, if no tags are
used then the Solidity compiler will interpret a `///` or `/**` comment
in the same way as if it were tagged with `@notice`.
used then the Solidity compiler will interpret a ``///`` or ``/**`` comment
in the same way as if it were tagged with ``@notice``.
=========== =============================================================================== =============================
Tag Context

View File

@ -1,2 +1,2 @@
sphinx_rtd_theme>=0.3.1
pygments-lexer-solidity>=0.3.1
pygments-lexer-solidity>=0.5.1

View File

@ -491,7 +491,8 @@ Horn clauses, where the lifecycle of the contract is represented by a loop
that can visit every public/external function non-deterministically. This way,
the behavior of the entire contract over an unbounded number of transactions
is taken into account when analyzing any function. Loops are fully supported
by this engine. Function calls are currently unsupported.
by this engine. Internal function calls are supported, but external function
calls are currently unsupported.
The CHC engine is much more powerful than BMC in terms of what it can prove,
and might require more computing resources.
@ -505,10 +506,16 @@ erasing knowledge or using a non-precise type). If it determines that a
verification target is safe, it is indeed safe, that is, there are no false
negatives (unless there is a bug in the SMTChecker).
Function calls to the same contract (or base contracts) are inlined when
possible, that is, when their implementation is available.
Calls to functions in other contracts are not inlined even if their code is
In the BMC engine, function calls to the same contract (or base contracts) are
inlined when possible, that is, when their implementation is available. Calls
to functions in other contracts are not inlined even if their code is
available, since we cannot guarantee that the actual deployed code is the same.
The CHC engine creates nonlinear Horn clauses that use summaries of the called
functions to support internal function calls. The same approach can and will be
used for external function calls, but the latter requires more work regarding
the entire state of the blockchain and is still unimplemented.
Complex pure functions are abstracted by an uninterpreted function (UF) over
the arguments.
@ -519,11 +526,14 @@ the arguments.
+-----------------------------------+--------------------------------------+
|``require`` |Assumption |
+-----------------------------------+--------------------------------------+
|internal |Inline function call |
|internal |BMC: Inline function call |
| |CHC: Function summaries |
+-----------------------------------+--------------------------------------+
|external |Inline function call |
| |Erase knowledge about state variables |
| |and local storage references |
|external |BMC: Inline function call or |
| |erase knowledge about state variables |
| |and local storage references. |
| |CHC: Function summaries and erase |
| |state knowledge. |
+-----------------------------------+--------------------------------------+
|``gasleft``, ``blockhash``, |Abstracted with UF |
|``keccak256``, ``ecrecover`` | |
@ -534,8 +544,8 @@ the arguments.
|implementation (external or | |
|complex) | |
+-----------------------------------+--------------------------------------+
|external functions without |Unsupported |
|implementation | |
|external functions without |BMC: Unsupported |
|implementation |CHC: Nondeterministic summary |
+-----------------------------------+--------------------------------------+
|others |Currently unsupported |
+-----------------------------------+--------------------------------------+

View File

@ -1,22 +0,0 @@
#################
Solidity in Depth
#################
This section should provide you with all you need to know about Solidity.
If something is missing here, please contact us on
`Gitter <https://gitter.im/ethereum/solidity>`_ or create a pull request on
`Github <https://github.com/ethereum/solidity/pulls>`_.
.. toctree::
:maxdepth: 2
layout-of-source-files.rst
structure-of-a-contract.rst
types.rst
units-and-global-variables.rst
control-structures.rst
contracts.rst
assembly.rst
miscellaneous.rst
050-breaking-changes.rst
060-breaking-changes.rst

View File

@ -24,8 +24,11 @@ solidity code. The goal of this guide is *consistency*. A quote from python's
`pep8 <https://www.python.org/dev/peps/pep-0008/#a-foolish-consistency-is-the-hobgoblin-of-little-minds>`_
captures this concept well.
.. note::
A style guide is about consistency. Consistency with this style guide is important. Consistency within a project is more important. Consistency within one module or function is most important.
But most importantly: know when to be inconsistent -- sometimes the style guide just doesn't apply. When in doubt, use your best judgement. Look at other examples and decide what looks best. And don't hesitate to ask!
But most importantly: **know when to be inconsistent** -- sometimes the style guide just doesn't apply. When in doubt, use your best judgement. Look at other examples and decide what looks best. And don't hesitate to ask!
***********
@ -383,8 +386,7 @@ No::
function spam(uint i , Coin coin) public ;
More than one space around an assignment or other operator to align with
another:
More than one space around an assignment or other operator to align with another:
Yes::
@ -996,7 +998,7 @@ Contract and Library Names
* Contract and library names should also match their filenames.
* If a contract file includes multiple contracts and/or libraries, then the filename should match the *core contract*. This is not recommended however if it can be avoided.
As shown in the example below, if the contract name is `Congress` and the library name is `Owned`, then their associated filenames should be `Congress.sol` and `Owned.sol`.
As shown in the example below, if the contract name is ``Congress`` and the library name is ``Owned``, then their associated filenames should be ``Congress.sol`` and ``Owned.sol``.
Yes::
@ -1132,8 +1134,8 @@ Solidity contracts can have a form of comments that are the basis of the
Ethereum Natural Language Specification Format.
Add comments above functions or contracts following `doxygen <http://www.doxygen.nl>`_ notation
of one or multiple lines starting with `///` or a
multiline comment starting with `/**` and ending with `*/`.
of one or multiple lines starting with ``///`` or a
multiline comment starting with ``/**`` and ending with ``*/``.
For example, the contract from `a simple smart contract <simple-smart-contract>`_ with the comments
added looks like the one below::

View File

@ -16,7 +16,7 @@ operators. For a quick reference of the various operators, see :ref:`order`.
The concept of "undefined" or "null" values does not exist in Solidity, but newly
declared variables always have a :ref:`default value<default-value>` dependent
on its type. To handle any unexpected values, you should use the :ref:`revert function<assert-and-require>` to revert the whole transaction, or return a
tuple with a second `bool` value denoting success.
tuple with a second ``bool`` value denoting success.
.. include:: types/value-types.rst
@ -26,4 +26,4 @@ tuple with a second `bool` value denoting success.
.. include:: types/operators.rst
.. include:: types/conversion.rst
.. include:: types/conversion.rst

View File

@ -138,8 +138,8 @@ the ``sum`` function iterates over to sum all the values.
if (keyIndex > 0)
return true;
else {
self.keys.push();
keyIndex = self.keys.length;
self.keys.push();
self.data[key].keyIndex = keyIndex + 1;
self.keys[keyIndex].key = key;
self.size++;

View File

@ -332,7 +332,7 @@ the :ref:`address type<address>`.
Before version 0.5.0, contracts directly derived from the address type
and there was no distinction between ``address`` and ``address payable``.
If you declare a local variable of contract type (`MyContract c`), you can call
If you declare a local variable of contract type (``MyContract c``), you can call
functions on that contract. Take care to assign it from somewhere that is the
same contract type.

View File

@ -140,15 +140,19 @@ Error Handling
See the dedicated section on :ref:`assert and require<assert-and-require>` for
more details on error handling and when to use which function.
``assert(bool condition)``:
``assert(bool condition)``
causes an invalid opcode and thus state change reversion if the condition is not met - to be used for internal errors.
``require(bool condition)``:
``require(bool condition)``
reverts if the condition is not met - to be used for errors in inputs or external components.
``require(bool condition, string memory message)``:
``require(bool condition, string memory message)``
reverts if the condition is not met - to be used for errors in inputs or external components. Also provides an error message.
``revert()``:
``revert()``
abort execution and revert state changes
``revert(string memory reason)``:
``revert(string memory reason)``
abort execution and revert state changes, providing an explanatory string
.. index:: keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography,
@ -156,32 +160,32 @@ more details on error handling and when to use which function.
Mathematical and Cryptographic Functions
----------------------------------------
``addmod(uint x, uint y, uint k) returns (uint)``:
``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``. Assert that ``k != 0`` starting from version 0.5.0.
``mulmod(uint x, uint y, uint k) returns (uint)``:
``mulmod(uint x, uint y, uint k) returns (uint)``
compute ``(x * y) % k`` where the multiplication is performed with arbitrary precision and does not wrap around at ``2**256``. Assert that ``k != 0`` starting from version 0.5.0.
``keccak256(bytes memory) returns (bytes32)``:
``keccak256(bytes memory) returns (bytes32)``
compute the Keccak-256 hash of the input
.. note::
There used to be an alias for ``keccak256`` called ``sha3``, which was removed in version 0.5.0.
``sha256(bytes memory) returns (bytes32)``:
``sha256(bytes memory) returns (bytes32)``
compute the SHA-256 hash of the input
``ripemd160(bytes memory) returns (bytes20)``:
``ripemd160(bytes memory) returns (bytes20)``
compute RIPEMD-160 hash of the input
``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``:
``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.
The function parameters correspond to ECDSA values of the signature:
``r`` = first 32 bytes of signature
``s`` = second 32 bytes of signature
``v`` = final 1 byte of signature
* ``r`` = first 32 bytes of signature
* ``s`` = second 32 bytes of signature
* ``v`` = final 1 byte of signature
``ecrecover`` returns an ``address``, and not an ``address payable``. See :ref:`address payable<address>` for
conversion, in case you need to transfer funds to the recovered address.
@ -209,17 +213,22 @@ Mathematical and Cryptographic Functions
Members of Address Types
------------------------
``<address>.balance`` (``uint256``):
``<address>.balance`` (``uint256``)
balance of the :ref:`address` in Wei
``<address payable>.transfer(uint256 amount)``:
``<address payable>.transfer(uint256 amount)``
send given amount of Wei to :ref:`address`, reverts on failure, forwards 2300 gas stipend, not adjustable
``<address payable>.send(uint256 amount) returns (bool)``:
``<address payable>.send(uint256 amount) returns (bool)``
send given amount of Wei to :ref:`address`, returns ``false`` on failure, forwards 2300 gas stipend, not adjustable
``<address>.call(bytes memory) returns (bool, bytes memory)``:
``<address>.call(bytes memory) returns (bool, bytes memory)``
issue low-level ``CALL`` with the given payload, returns success condition and return data, forwards all available gas, adjustable
``<address>.delegatecall(bytes memory) returns (bool, bytes memory)``:
``<address>.delegatecall(bytes memory) returns (bool, bytes memory)``
issue low-level ``DELEGATECALL`` with the given payload, returns success condition and return data, forwards all available gas, adjustable
``<address>.staticcall(bytes memory) returns (bool, bytes memory)``:
``<address>.staticcall(bytes memory) returns (bool, bytes memory)``
issue low-level ``STATICCALL`` with the given payload, returns success condition and return data, forwards all available gas, adjustable
For more information, see the section on :ref:`address`.
@ -258,10 +267,10 @@ For more information, see the section on :ref:`address`.
Contract Related
----------------
``this`` (current contract's type):
``this`` (current contract's type)
the current contract, explicitly convertible to :ref:`address`
``selfdestruct(address payable recipient)``:
``selfdestruct(address payable recipient)``
Destroy the current contract, sending its funds to the given :ref:`address`
and end execution.
Note that ``selfdestruct`` has some peculiarities inherited from the EVM:
@ -290,10 +299,10 @@ type ``X``. Currently, there is limited support for this feature, but
it might be expanded in the future. The following properties are
available for a contract type ``C``:
``type(C).name``:
``type(C).name``
The name of the contract.
``type(C).creationCode``:
``type(C).creationCode``
Memory byte array that contains the creation bytecode of the contract.
This can be used in inline assembly to build custom creation routines,
especially by using the ``create2`` opcode.
@ -301,7 +310,7 @@ available for a contract type ``C``:
derived contract. It causes the bytecode to be included in the bytecode
of the call site and thus circular references like that are not possible.
``type(C).runtimeCode``:
``type(C).runtimeCode``
Memory byte array that contains the runtime bytecode of the contract.
This is the code that is usually deployed by the constructor of ``C``.
If ``C`` has a constructor that uses inline assembly, this might be
@ -310,3 +319,12 @@ available for a contract type ``C``:
regular calls.
The same restrictions as with ``.creationCode`` also apply for this
property.
In addition to the properties above, the following properties are available
for an interface type ``I``:
``type(I).interfaceId``:
A ``bytes4`` value containing the `EIP-165 <https://eips.ethereum.org/EIPS/eip-165>`_
interface identifier of the given interface ``I``. This identifier is defined as the ``XOR`` of all
function selectors defined within the interface itself - excluding all inherited functions.

View File

@ -106,7 +106,8 @@ Target options
Below is a list of target EVM versions and the compiler-relevant changes introduced
at each version. Backward compatibility is not guaranteed between each version.
- ``homestead`` (oldest version)
- ``homestead``
- (oldest version)
- ``tangerineWhistle``
- Gas cost for access to other accounts increased, relevant for gas estimation and the optimizer.
- All gas sent by default for external calls, previously a certain amount had to be retained.
@ -692,7 +693,7 @@ Review changes
The command above applies all changes as shown below. Please review them carefully.
.. code-block:: none
.. code-block:: solidity
pragma solidity >=0.6.0 <0.7.0;

View File

@ -70,7 +70,7 @@ The following example program is written in the EVM dialect and computes exponen
It can be compiled using ``solc --strict-assembly``. The builtin functions
``mul`` and ``div`` compute product and division, respectively.
.. code::
.. code-block:: yul
{
function power(base, exponent) -> result
@ -91,7 +91,7 @@ It is also possible to implement the same function using a for-loop
instead of with recursion. Here, ``lt(a, b)`` computes whether ``a`` is less than ``b``.
less-than comparison.
.. code::
.. code-block:: yul
{
function power(base, exponent) -> result
@ -115,7 +115,7 @@ This will use the :ref:`Yul object notation <yul-object>` so that it is possible
to code as data to deploy contracts. This Yul mode is available for the commandline compiler
(use ``--strict-assembly``) and for the :ref:`standard-json interface <compiler-api>`:
::
.. code-block:: json
{
"language": "Yul",
@ -180,14 +180,14 @@ bitwise ``and`` with the string "abc" is computed.
The final value is assigned to a local variable called ``x``.
Strings are stored left-aligned and cannot be longer than 32 bytes.
.. code::
.. code-block:: yul
let x := and("abc", add(3, 2))
Unless it is the default type, the type of a literal
has to be specified after a colon:
.. code::
.. code-block:: yul
let x := and("abc":uint32, add(3:uint256, 2:uint256))
@ -201,7 +201,7 @@ If the function returns a single value, it can be directly used
inside an expression again. If it returns multiple values,
they have to be assigned to local variables.
.. code::
.. code-block:: yul
mstore(0x80, add(mload(0x80), 3))
// Here, the user-defined function `f` returns
@ -242,7 +242,7 @@ Future dialects migh introduce specific types for such pointers.
When a variable is referenced, its current value is copied.
For the EVM, this translates to a ``DUP`` instruction.
.. code::
.. code-block:: yul
{
let zero := 0
@ -260,7 +260,7 @@ you denote that following a colon. You can also declare multiple
variables in one statement when you assign from a function call
that returns multiple values.
.. code::
.. code-block:: yul
{
let zero:uint32 := 0:uint32
@ -283,7 +283,7 @@ values have to match.
If you want to assign the values returned from a function that has
multiple return parameters, you have to provide multiple variables.
.. code::
.. code-block:: yul
let v := 0
// re-assign v
@ -301,7 +301,7 @@ The if statement can be used for conditionally executing code.
No "else" block can be defined. Consider using "switch" instead (see below) if
you need multiple alternatives.
.. code::
.. code-block:: yul
if eq(value, 0) { revert(0, 0) }
@ -317,7 +317,7 @@ Contrary to other programming languages, for safety reasons, control flow does
not continue from one case to the next. There can be a fallback or default
case called ``default`` which is taken if none of the literal constants matches.
.. code::
.. code-block:: yul
{
let x := 0
@ -349,7 +349,7 @@ or skip to the post-part, respectively.
The following example computes the sum of an area in memory.
.. code::
.. code-block:: yul
{
let x := 0
@ -361,7 +361,7 @@ The following example computes the sum of an area in memory.
For loops can also be used as a replacement for while loops:
Simply leave the initialization and post-iteration parts empty.
.. code::
.. code-block:: yul
{
let x := 0
@ -404,7 +404,7 @@ the current yul function.
The following example implements the power function by square-and-multiply.
.. code::
.. code-block:: yul
{
function power(base, exponent) -> result {
@ -425,7 +425,7 @@ Specification of Yul
This chapter describes Yul code formally. Yul code is usually placed inside Yul objects,
which are explained in their own chapter.
Grammar::
.. code-block:: none
Block = '{' Statement* '}'
Statement =
@ -588,7 +588,7 @@ For an identifier ``v``, let ``$v`` be the name of the identifier.
We will use a destructuring notation for the AST nodes.
.. code::
.. code-block:: none
E(G, L, <{St1, ..., Stn}>: Block) =
let G1, L1, mode = E(G, L, St1, ..., Stn)
@ -915,7 +915,7 @@ Hex strings can be used to specify data in hex encoding,
regular strings in native encoding. For code,
``datacopy`` will access its assembled binary representation.
Grammar::
.. code-block:: none
Object = 'object' StringLiteral '{' Code ( Object | Data )* '}'
Code = 'code' Block
@ -927,7 +927,7 @@ Above, ``Block`` refers to ``Block`` in the Yul code grammar explained in the pr
An example Yul Object is shown below:
.. code::
.. code-block:: yul
// A contract consists of a single object with sub-objects representing
// the code to be deployed or other contracts it can create.
@ -1010,7 +1010,7 @@ for more details about its internals.
If you want to use Solidity in stand-alone Yul mode, you activate the optimizer using ``--optimize``:
::
.. code-block:: sh
solc --strict-assembly --optimize

View File

@ -22,15 +22,17 @@
*/
#include <libevmasm/ExpressionClasses.h>
#include <utility>
#include <tuple>
#include <functional>
#include <boost/range/adaptor/reversed.hpp>
#include <boost/noncopyable.hpp>
#include <libevmasm/Assembly.h>
#include <libevmasm/CommonSubexpressionEliminator.h>
#include <libevmasm/SimplificationRules.h>
#include <boost/range/adaptor/reversed.hpp>
#include <boost/noncopyable.hpp>
#include <functional>
#include <tuple>
#include <utility>
using namespace std;
using namespace solidity;
using namespace solidity::evmasm;

View File

@ -28,6 +28,7 @@
#include <ostream>
#include <tuple>
#include <utility>
namespace solidity::evmasm
{
@ -119,7 +120,7 @@ public:
struct GasConsumption
{
GasConsumption(unsigned _value = 0, bool _infinite = false): value(_value), isInfinite(_infinite) {}
GasConsumption(u256 _value, bool _infinite = false): value(_value), isInfinite(_infinite) {}
GasConsumption(u256 _value, bool _infinite = false): value(std::move(_value)), isInfinite(_infinite) {}
static GasConsumption infinite() { return GasConsumption(0, true); }
GasConsumption& operator+=(GasConsumption const& _other);
@ -133,8 +134,8 @@ public:
};
/// Constructs a new gas meter given the current state.
GasMeter(std::shared_ptr<KnownState> const& _state, langutil::EVMVersion _evmVersion, u256 const& _largestMemoryAccess = 0):
m_state(_state), m_evmVersion(_evmVersion), m_largestMemoryAccess(_largestMemoryAccess) {}
GasMeter(std::shared_ptr<KnownState> _state, langutil::EVMVersion _evmVersion, u256 _largestMemoryAccess = 0):
m_state(std::move(_state)), m_evmVersion(_evmVersion), m_largestMemoryAccess(std::move(_largestMemoryAccess)) {}
/// @returns an upper bound on the gas consumed by the given instruction and updates
/// the state.

View File

@ -23,6 +23,7 @@
#pragma once
#include <utility>
#include <vector>
#include <map>
#include <set>
@ -83,7 +84,7 @@ public:
explicit KnownState(
std::shared_ptr<ExpressionClasses> _expressionClasses = std::make_shared<ExpressionClasses>()
): m_expressionClasses(_expressionClasses)
): m_expressionClasses(std::move(_expressionClasses))
{
}

View File

@ -232,6 +232,28 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart4(
};
}
template <class Pattern>
std::vector<SimplificationRule<Pattern>> simplificationRuleListPart4_5(
Pattern,
Pattern,
Pattern,
Pattern X,
Pattern Y
)
{
using Builtins = typename Pattern::Builtins;
return std::vector<SimplificationRule<Pattern>>{
// idempotent operations
{Builtins::AND(Builtins::AND(X, Y), Y), [=]{ return Builtins::AND(X, Y); }, true},
{Builtins::AND(Y, Builtins::AND(X, Y)), [=]{ return Builtins::AND(X, Y); }, true},
{Builtins::AND(Builtins::AND(Y, X), Y), [=]{ return Builtins::AND(Y, X); }, true},
{Builtins::AND(Y, Builtins::AND(Y, X)), [=]{ return Builtins::AND(Y, X); }, true},
{Builtins::OR(Builtins::OR(X, Y), Y), [=]{ return Builtins::OR(X, Y); }, true},
{Builtins::OR(Y, Builtins::OR(X, Y)), [=]{ return Builtins::OR(X, Y); }, true},
{Builtins::OR(Builtins::OR(Y, X), Y), [=]{ return Builtins::OR(Y, X); }, true},
{Builtins::OR(Y, Builtins::OR(Y, X)), [=]{ return Builtins::OR(Y, X); }, true},
};
}
template <class Pattern>
std::vector<SimplificationRule<Pattern>> simplificationRuleListPart5(
@ -663,6 +685,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleList(
rules += simplificationRuleListPart2(A, B, C, W, X);
rules += simplificationRuleListPart3(A, B, C, W, X);
rules += simplificationRuleListPart4(A, B, C, W, X);
rules += simplificationRuleListPart4_5(A, B, C, W, X);
rules += simplificationRuleListPart5(A, B, C, W, X);
rules += simplificationRuleListPart6(A, B, C, W, X);
rules += simplificationRuleListPart7(A, B, C, W, X);

View File

@ -55,6 +55,7 @@
#include <cstdint>
#include <string>
#include <tuple>
#include <utility>
namespace solidity::langutil
{
@ -68,8 +69,8 @@ class CharStream
{
public:
CharStream() = default;
explicit CharStream(std::string const& _source, std::string const& name):
m_source(_source), m_name(name) {}
explicit CharStream(std::string _source, std::string name):
m_source(std::move(_source)), m_name(std::move(name)) {}
int position() const { return m_position; }
bool isPastEndOfInput(size_t _charsForward = 0) const { return (m_position + _charsForward) >= m_source.size(); }

View File

@ -22,15 +22,16 @@
#pragma once
#include <string>
#include <utility>
#include <vector>
#include <memory>
#include <libsolutil/Exceptions.h>
#include <libsolutil/Assertions.h>
#include <libsolutil/CommonData.h>
#include <liblangutil/SourceLocation.h>
#include <string>
#include <utility>
#include <vector>
#include <memory>
namespace solidity::langutil
{
class Error;

View File

@ -23,7 +23,9 @@
#pragma once
#include <liblangutil/Token.h>
#include <string>
#include <utility>
#include <vector>
namespace solidity::langutil
@ -80,8 +82,8 @@ struct SemVerMatchExpression
class SemVerMatchExpressionParser
{
public:
SemVerMatchExpressionParser(std::vector<Token> const& _tokens, std::vector<std::string> const& _literals):
m_tokens(_tokens), m_literals(_literals)
SemVerMatchExpressionParser(std::vector<Token> _tokens, std::vector<std::string> _literals):
m_tokens(std::move(_tokens)), m_literals(std::move(_literals))
{}
SemVerMatchExpression parse();

View File

@ -21,7 +21,6 @@
#include <liblangutil/SourceReferenceFormatterHuman.h>
#include <liblangutil/Scanner.h>
#include <liblangutil/Exceptions.h>
#include <cmath>
#include <iomanip>
using namespace std;
@ -70,58 +69,66 @@ void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _
if (_ref.sourceName.empty())
return; // Nothing we can print here
int const leftpad = static_cast<int>(log10(max(_ref.position.line, 1))) + 1;
// line 0: source name
frameColored() << string(leftpad, ' ') << "--> ";
if (_ref.position.line < 0)
{
m_stream << _ref.sourceName << "\n";
frameColored() << "-->";
m_stream << ' ' << _ref.sourceName << '\n';
return; // No line available, nothing else to print
}
m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 1) << ":" << '\n';
string line = std::to_string(_ref.position.line + 1); // one-based line number as string
string leftpad = string(line.size(), ' ');
// line 0: source name
m_stream << leftpad;
frameColored() << "-->";
m_stream << ' ' << _ref.sourceName << ':' << line << ':' << (_ref.position.column + 1) << ":\n";
if (!_ref.multiline)
{
int const locationLength = _ref.endColumn - _ref.startColumn;
// line 1:
m_stream << string(leftpad, ' ');
frameColored() << " |" << '\n';
m_stream << leftpad << ' ';
frameColored() << '|';
m_stream << '\n';
// line 2:
frameColored() << (_ref.position.line + 1) << " | ";
m_stream << _ref.text.substr(0, _ref.startColumn);
frameColored() << line << " |";
m_stream << ' ' << _ref.text.substr(0, _ref.startColumn);
highlightColored() << _ref.text.substr(_ref.startColumn, locationLength);
m_stream << _ref.text.substr(_ref.endColumn) << '\n';
// line 3:
m_stream << string(leftpad, ' ');
frameColored() << " | ";
m_stream << leftpad << ' ';
frameColored() << '|';
m_stream << ' ';
for_each(
_ref.text.cbegin(),
_ref.text.cbegin() + _ref.startColumn,
[this](char ch) { m_stream << (ch == '\t' ? '\t' : ' '); }
);
diagColored() << string(locationLength, '^') << '\n';
diagColored() << string(locationLength, '^');
m_stream << '\n';
}
else
{
// line 1:
m_stream << string(leftpad, ' ');
frameColored() << " |" << '\n';
m_stream << leftpad << ' ';
frameColored() << '|';
m_stream << '\n';
// line 2:
frameColored() << (_ref.position.line + 1) << " | ";
m_stream << _ref.text.substr(0, _ref.startColumn);
frameColored() << line << " |";
m_stream << ' ' << _ref.text.substr(0, _ref.startColumn);
highlightColored() << _ref.text.substr(_ref.startColumn) << '\n';
// line 3:
frameColored() << string(leftpad, ' ') << " | ";
m_stream << string(_ref.startColumn, ' ');
diagColored() << "^ (Relevant source part starts here and spans across multiple lines).\n";
m_stream << leftpad << ' ';
frameColored() << '|';
m_stream << ' ' << string(_ref.startColumn, ' ');
diagColored() << "^ (Relevant source part starts here and spans across multiple lines).";
m_stream << '\n';
}
}

View File

@ -12,6 +12,8 @@ set(sources
analysis/ControlFlowGraph.h
analysis/DeclarationContainer.cpp
analysis/DeclarationContainer.h
analysis/DeclarationTypeChecker.cpp
analysis/DeclarationTypeChecker.h
analysis/DocStringAnalyser.cpp
analysis/DocStringAnalyser.h
analysis/ImmutableValidator.cpp
@ -73,6 +75,8 @@ set(sources
codegen/LValue.h
codegen/MultiUseYulFunctionCollector.h
codegen/MultiUseYulFunctionCollector.cpp
codegen/ReturnInfo.h
codegen/ReturnInfo.cpp
codegen/YulUtilFunctions.h
codegen/YulUtilFunctions.cpp
codegen/ir/IRGenerator.cpp

View File

@ -24,6 +24,8 @@
#include <libsolidity/ast/ASTVisitor.h>
#include <utility>
namespace solidity::langutil
{
class ErrorReporter;
@ -47,7 +49,7 @@ public:
):
m_errorReporter(_errorReporter),
m_depth(_newDepth),
m_types(_types)
m_types(std::move(_types))
{
}

View File

@ -55,7 +55,7 @@ bool ContractLevelChecker::check(ContractDefinition const& _contract)
checkDuplicateEvents(_contract);
m_overrideChecker.check(_contract);
checkBaseConstructorArguments(_contract);
checkAbstractFunctions(_contract);
checkAbstractDefinitions(_contract);
checkExternalTypeClashes(_contract);
checkHashCollisions(_contract);
checkLibraryRequirements(_contract);
@ -156,55 +156,43 @@ void ContractLevelChecker::findDuplicateDefinitions(map<string, vector<T>> const
}
}
void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _contract)
void ContractLevelChecker::checkAbstractDefinitions(ContractDefinition const& _contract)
{
// Mapping from name to function definition (exactly one per argument type equality class) and
// flag to indicate whether it is fully implemented.
using FunTypeAndFlag = std::pair<FunctionTypePointer, bool>;
map<string, vector<FunTypeAndFlag>> functions;
// Collects functions, static variable getters and modifiers. If they
// override (unimplemented) base class ones, they are replaced.
set<OverrideProxy, OverrideProxy::CompareBySignature> proxies;
auto registerFunction = [&](Declaration const& _declaration, FunctionTypePointer const& _type, bool _implemented)
auto registerProxy = [&proxies](OverrideProxy const& _overrideProxy)
{
auto& overloads = functions[_declaration.name()];
auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag)
{
return _type->hasEqualParameterTypes(*_funAndFlag.first);
});
if (it == overloads.end())
overloads.emplace_back(_type, _implemented);
else if (_implemented)
it->second = true;
// Overwrite an existing proxy, if it exists.
if (!_overrideProxy.unimplemented())
proxies.erase(_overrideProxy);
proxies.insert(_overrideProxy);
};
// Search from base to derived, collect all functions and update
// the 'implemented' flag.
// Search from base to derived, collect all functions and modifiers and
// update proxies.
for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts))
{
for (VariableDeclaration const* v: contract->stateVariables())
if (v->isPartOfExternalInterface())
registerFunction(*v, TypeProvider::function(*v), true);
registerProxy(OverrideProxy(v));
for (FunctionDefinition const* function: contract->definedFunctions())
if (!function->isConstructor())
registerFunction(
*function,
TypeProvider::function(*function)->asCallableFunction(false),
function->isImplemented()
);
registerProxy(OverrideProxy(function));
for (ModifierDefinition const* modifier: contract->functionModifiers())
registerProxy(OverrideProxy(modifier));
}
// Set to not fully implemented if at least one flag is false.
// Note that `_contract.annotation().unimplementedFunctions` has already been
// Note that `_contract.annotation().unimplementedDeclarations` has already been
// pre-filled by `checkBaseConstructorArguments`.
for (auto const& it: functions)
for (auto const& funAndFlag: it.second)
if (!funAndFlag.second)
{
FunctionDefinition const* function = dynamic_cast<FunctionDefinition const*>(&funAndFlag.first->declaration());
solAssert(function, "");
_contract.annotation().unimplementedFunctions.push_back(function);
break;
}
for (auto const& proxy: proxies)
if (proxy.unimplemented())
_contract.annotation().unimplementedDeclarations.push_back(proxy.declaration());
if (_contract.abstract())
{
@ -221,15 +209,16 @@ void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _con
if (
_contract.contractKind() == ContractKind::Contract &&
!_contract.abstract() &&
!_contract.annotation().unimplementedFunctions.empty()
!_contract.annotation().unimplementedDeclarations.empty()
)
{
SecondarySourceLocation ssl;
for (auto function: _contract.annotation().unimplementedFunctions)
ssl.append("Missing implementation:", function->location());
for (auto declaration: _contract.annotation().unimplementedDeclarations)
ssl.append("Missing implementation: ", declaration->location());
m_errorReporter.typeError(_contract.location(), ssl,
"Contract \"" + _contract.annotation().canonicalName
+ "\" should be marked as abstract.");
}
}
@ -277,7 +266,7 @@ void ContractLevelChecker::checkBaseConstructorArguments(ContractDefinition cons
if (FunctionDefinition const* constructor = contract->constructor())
if (contract != &_contract && !constructor->parameters().empty())
if (!_contract.annotation().baseConstructorArguments.count(constructor))
_contract.annotation().unimplementedFunctions.push_back(constructor);
_contract.annotation().unimplementedDeclarations.push_back(constructor);
}
void ContractLevelChecker::annotateBaseConstructorArguments(

View File

@ -61,7 +61,8 @@ private:
void checkDuplicateEvents(ContractDefinition const& _contract);
template <class T>
void findDuplicateDefinitions(std::map<std::string, std::vector<T>> const& _definitions, std::string _message);
void checkAbstractFunctions(ContractDefinition const& _contract);
/// Checks for unimplemented functions and modifiers.
void checkAbstractDefinitions(ContractDefinition const& _contract);
/// Checks that the base constructor arguments are properly provided.
/// Fills the list of unimplemented functions in _contract's annotations.
void checkBaseConstructorArguments(ContractDefinition const& _contract);

View File

@ -591,7 +591,7 @@ bool ControlFlowBuilder::visit(Identifier const& _identifier)
if (auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration))
m_currentNode->variableOccurrences.emplace_back(
*variableDeclaration,
static_cast<Expression const&>(_identifier).annotation().lValueRequested ?
static_cast<Expression const&>(_identifier).annotation().willBeWrittenTo ?
VariableOccurrence::Kind::Assignment :
VariableOccurrence::Kind::Access,
_identifier.location()

View File

@ -26,6 +26,7 @@
#include <map>
#include <memory>
#include <stack>
#include <utility>
#include <vector>
namespace solidity::frontend
@ -48,8 +49,8 @@ public:
Assignment,
InlineAssembly
};
VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, std::optional<langutil::SourceLocation> const& _occurrence = {}):
m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(_occurrence)
VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, std::optional<langutil::SourceLocation> _occurrence = {}):
m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(std::move(_occurrence))
{
}

View File

@ -0,0 +1,382 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libsolidity/analysis/DeclarationTypeChecker.h>
#include <libsolidity/analysis/ConstantEvaluator.h>
#include <libsolidity/ast/TypeProvider.h>
#include <liblangutil/ErrorReporter.h>
#include <libsolutil/Algorithms.h>
#include <boost/range/adaptor/transformed.hpp>
using namespace std;
using namespace solidity::langutil;
using namespace solidity::frontend;
bool DeclarationTypeChecker::visit(ElementaryTypeName const& _typeName)
{
if (_typeName.annotation().type)
return false;
_typeName.annotation().type = TypeProvider::fromElementaryTypeName(_typeName.typeName());
if (_typeName.stateMutability().has_value())
{
// for non-address types this was already caught by the parser
solAssert(_typeName.annotation().type->category() == Type::Category::Address, "");
switch (*_typeName.stateMutability())
{
case StateMutability::Payable:
_typeName.annotation().type = TypeProvider::payableAddress();
break;
case StateMutability::NonPayable:
_typeName.annotation().type = TypeProvider::address();
break;
default:
typeError(
_typeName.location(),
"Address types can only be payable or non-payable."
);
break;
}
}
return true;
}
bool DeclarationTypeChecker::visit(StructDefinition const& _struct)
{
if (_struct.annotation().recursive.has_value())
{
if (!m_currentStructsSeen.empty() && *_struct.annotation().recursive)
m_recursiveStructSeen = true;
return false;
}
if (m_currentStructsSeen.count(&_struct))
{
_struct.annotation().recursive = true;
m_recursiveStructSeen = true;
return false;
}
bool previousRecursiveStructSeen = m_recursiveStructSeen;
bool hasRecursiveChild = false;
m_currentStructsSeen.insert(&_struct);
for (auto const& member: _struct.members())
{
m_recursiveStructSeen = false;
member->accept(*this);
solAssert(member->annotation().type, "");
solAssert(member->annotation().type->canBeStored(), "Type cannot be used in struct.");
if (m_recursiveStructSeen)
hasRecursiveChild = true;
}
if (!_struct.annotation().recursive.has_value())
_struct.annotation().recursive = hasRecursiveChild;
m_recursiveStructSeen = previousRecursiveStructSeen || *_struct.annotation().recursive;
m_currentStructsSeen.erase(&_struct);
if (m_currentStructsSeen.empty())
m_recursiveStructSeen = false;
// Check direct recursion, fatal error if detected.
auto visitor = [&](StructDefinition const& _struct, auto& _cycleDetector, size_t _depth)
{
if (_depth >= 256)
fatalDeclarationError(_struct.location(), "Struct definition exhausts cyclic dependency validator.");
for (ASTPointer<VariableDeclaration> const& member: _struct.members())
{
Type const* memberType = member->annotation().type;
while (auto arrayType = dynamic_cast<ArrayType const*>(memberType))
{
if (arrayType->isDynamicallySized())
break;
memberType = arrayType->baseType();
}
if (auto structType = dynamic_cast<StructType const*>(memberType))
if (_cycleDetector.run(structType->structDefinition()))
return;
}
};
if (util::CycleDetector<StructDefinition>(visitor).run(_struct) != nullptr)
fatalTypeError(_struct.location(), "Recursive struct definition.");
return false;
}
void DeclarationTypeChecker::endVisit(UserDefinedTypeName const& _typeName)
{
if (_typeName.annotation().type)
return;
Declaration const* declaration = _typeName.annotation().referencedDeclaration;
solAssert(declaration, "");
if (StructDefinition const* structDef = dynamic_cast<StructDefinition const*>(declaration))
{
if (!m_insideFunctionType && !m_currentStructsSeen.empty())
structDef->accept(*this);
_typeName.annotation().type = TypeProvider::structType(*structDef, DataLocation::Storage);
}
else if (EnumDefinition const* enumDef = dynamic_cast<EnumDefinition const*>(declaration))
_typeName.annotation().type = TypeProvider::enumType(*enumDef);
else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration))
_typeName.annotation().type = TypeProvider::contract(*contract);
else
{
_typeName.annotation().type = TypeProvider::emptyTuple();
fatalTypeError(_typeName.location(), "Name has to refer to a struct, enum or contract.");
}
}
bool DeclarationTypeChecker::visit(FunctionTypeName const& _typeName)
{
if (_typeName.annotation().type)
return false;
bool previousInsideFunctionType = m_insideFunctionType;
m_insideFunctionType = true;
_typeName.parameterTypeList()->accept(*this);
_typeName.returnParameterTypeList()->accept(*this);
m_insideFunctionType = previousInsideFunctionType;
switch (_typeName.visibility())
{
case Visibility::Internal:
case Visibility::External:
break;
default:
fatalTypeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\".");
return false;
}
if (_typeName.isPayable() && _typeName.visibility() != Visibility::External)
{
fatalTypeError(_typeName.location(), "Only external function types can be payable.");
return false;
}
_typeName.annotation().type = TypeProvider::function(_typeName);
return false;
}
void DeclarationTypeChecker::endVisit(Mapping const& _mapping)
{
if (_mapping.annotation().type)
return;
if (auto const* typeName = dynamic_cast<UserDefinedTypeName const*>(&_mapping.keyType()))
{
if (auto const* contractType = dynamic_cast<ContractType const*>(typeName->annotation().type))
{
if (contractType->contractDefinition().isLibrary())
m_errorReporter.fatalTypeError(
typeName->location(),
"Library types cannot be used as mapping keys."
);
}
else if (typeName->annotation().type->category() != Type::Category::Enum)
m_errorReporter.fatalTypeError(
typeName->location(),
"Only elementary types, contract types or enums are allowed as mapping keys."
);
}
else
solAssert(dynamic_cast<ElementaryTypeName const*>(&_mapping.keyType()), "");
TypePointer keyType = _mapping.keyType().annotation().type;
TypePointer valueType = _mapping.valueType().annotation().type;
// Convert key type to memory.
keyType = TypeProvider::withLocationIfReference(DataLocation::Memory, keyType);
// Convert value type to storage reference.
valueType = TypeProvider::withLocationIfReference(DataLocation::Storage, valueType);
_mapping.annotation().type = TypeProvider::mapping(keyType, valueType);
}
void DeclarationTypeChecker::endVisit(ArrayTypeName const& _typeName)
{
if (_typeName.annotation().type)
return;
TypePointer baseType = _typeName.baseType().annotation().type;
if (!baseType)
{
solAssert(!m_errorReporter.errors().empty(), "");
return;
}
if (baseType->storageBytes() == 0)
fatalTypeError(_typeName.baseType().location(), "Illegal base type of storage size zero for array.");
if (Expression const* length = _typeName.length())
{
TypePointer& lengthTypeGeneric = length->annotation().type;
if (!lengthTypeGeneric)
lengthTypeGeneric = ConstantEvaluator(m_errorReporter).evaluate(*length);
RationalNumberType const* lengthType = dynamic_cast<RationalNumberType const*>(lengthTypeGeneric);
u256 lengthValue = 0;
if (!lengthType || !lengthType->mobileType())
typeError(length->location(), "Invalid array length, expected integer literal or constant expression.");
else if (lengthType->isZero())
typeError(length->location(), "Array with zero length specified.");
else if (lengthType->isFractional())
typeError(length->location(), "Array with fractional length specified.");
else if (lengthType->isNegative())
typeError(length->location(), "Array with negative length specified.");
else
lengthValue = lengthType->literalValue(nullptr);
_typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType, lengthValue);
}
else
_typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType);
}
void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
{
if (_variable.annotation().type)
return;
if (_variable.isConstant() && !_variable.isStateVariable())
m_errorReporter.declarationError(_variable.location(), "The \"constant\" keyword can only be used for state variables.");
if (_variable.immutable() && !_variable.isStateVariable())
m_errorReporter.declarationError(_variable.location(), "The \"immutable\" keyword can only be used for state variables.");
if (!_variable.typeName())
{
// This can still happen in very unusual cases where a developer uses constructs, such as
// `var a;`, however, such code will have generated errors already.
// However, we cannot blindingly solAssert() for that here, as the TypeChecker (which is
// invoking ReferencesResolver) is generating it, so the error is most likely(!) generated
// after this step.
return;
}
using Location = VariableDeclaration::Location;
Location varLoc = _variable.referenceLocation();
DataLocation typeLoc = DataLocation::Memory;
set<Location> allowedDataLocations = _variable.allowedDataLocations();
if (!allowedDataLocations.count(varLoc))
{
auto locationToString = [](VariableDeclaration::Location _location) -> string
{
switch (_location)
{
case Location::Memory: return "\"memory\"";
case Location::Storage: return "\"storage\"";
case Location::CallData: return "\"calldata\"";
case Location::Unspecified: return "none";
}
return {};
};
string errorString;
if (!_variable.hasReferenceOrMappingType())
errorString = "Data location can only be specified for array, struct or mapping types";
else
{
errorString = "Data location must be " +
util::joinHumanReadable(
allowedDataLocations | boost::adaptors::transformed(locationToString),
", ",
" or "
);
if (_variable.isCallableOrCatchParameter())
errorString +=
" for " +
string(_variable.isReturnParameter() ? "return " : "") +
"parameter in" +
string(_variable.isExternalCallableParameter() ? " external" : "") +
" function";
else
errorString += " for variable";
}
errorString += ", but " + locationToString(varLoc) + " was given.";
typeError(_variable.location(), errorString);
solAssert(!allowedDataLocations.empty(), "");
varLoc = *allowedDataLocations.begin();
}
// Find correct data location.
if (_variable.isEventParameter())
{
solAssert(varLoc == Location::Unspecified, "");
typeLoc = DataLocation::Memory;
}
else if (_variable.isStateVariable())
{
solAssert(varLoc == Location::Unspecified, "");
typeLoc = (_variable.isConstant() || _variable.immutable()) ? DataLocation::Memory : DataLocation::Storage;
}
else if (
dynamic_cast<StructDefinition const*>(_variable.scope()) ||
dynamic_cast<EnumDefinition const*>(_variable.scope())
)
// The actual location will later be changed depending on how the type is used.
typeLoc = DataLocation::Storage;
else
switch (varLoc)
{
case Location::Memory:
typeLoc = DataLocation::Memory;
break;
case Location::Storage:
typeLoc = DataLocation::Storage;
break;
case Location::CallData:
typeLoc = DataLocation::CallData;
break;
case Location::Unspecified:
solAssert(!_variable.hasReferenceOrMappingType(), "Data location not properly set.");
}
TypePointer type = _variable.typeName()->annotation().type;
if (auto ref = dynamic_cast<ReferenceType const*>(type))
{
bool isPointer = !_variable.isStateVariable();
type = TypeProvider::withLocation(ref, typeLoc, isPointer);
}
_variable.annotation().type = type;
}
void DeclarationTypeChecker::typeError(SourceLocation const& _location, string const& _description)
{
m_errorOccurred = true;
m_errorReporter.typeError(_location, _description);
}
void DeclarationTypeChecker::fatalTypeError(SourceLocation const& _location, string const& _description)
{
m_errorOccurred = true;
m_errorReporter.fatalTypeError(_location, _description);
}
void DeclarationTypeChecker::fatalDeclarationError(SourceLocation const& _location, string const& _description)
{
m_errorOccurred = true;
m_errorReporter.fatalDeclarationError(_location, _description);
}
bool DeclarationTypeChecker::check(ASTNode const& _node)
{
_node.accept(*this);
return !m_errorOccurred;
}

View File

@ -0,0 +1,79 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/ast/ASTAnnotations.h>
#include <liblangutil/EVMVersion.h>
#include <boost/noncopyable.hpp>
#include <list>
#include <map>
namespace solidity::langutil
{
class ErrorReporter;
}
namespace solidity::frontend
{
/**
* Assigns types to declarations.
*/
class DeclarationTypeChecker: private ASTConstVisitor
{
public:
DeclarationTypeChecker(
langutil::ErrorReporter& _errorReporter,
langutil::EVMVersion _evmVersion
):
m_errorReporter(_errorReporter),
m_evmVersion(_evmVersion)
{}
bool check(ASTNode const& _contract);
private:
bool visit(ElementaryTypeName const& _typeName) override;
void endVisit(UserDefinedTypeName const& _typeName) override;
bool visit(FunctionTypeName const& _typeName) override;
void endVisit(Mapping const& _mapping) override;
void endVisit(ArrayTypeName const& _typeName) override;
void endVisit(VariableDeclaration const& _variable) override;
bool visit(StructDefinition const& _struct) override;
/// Adds a new error to the list of errors.
void typeError(langutil::SourceLocation const& _location, std::string const& _description);
/// Adds a new error to the list of errors and throws to abort reference resolving.
void fatalTypeError(langutil::SourceLocation const& _location, std::string const& _description);
/// Adds a new error to the list of errors and throws to abort reference resolving.
void fatalDeclarationError(langutil::SourceLocation const& _location, std::string const& _description);
langutil::ErrorReporter& m_errorReporter;
bool m_errorOccurred = false;
langutil::EVMVersion m_evmVersion;
bool m_insideFunctionType = false;
bool m_recursiveStructSeen = false;
std::set<StructDefinition const*> m_currentStructsSeen;
};
}

View File

@ -148,7 +148,8 @@ bool ImmutableValidator::analyseCallable(CallableDeclaration const& _callableDec
funcDef->body().accept(*this);
}
else if (ModifierDefinition const* modDef = dynamic_cast<decltype(modDef)>(&_callableDeclaration))
modDef->body().accept(*this);
if (modDef->isImplemented())
modDef->body().accept(*this);
m_currentConstructor = prevConstructor;
@ -160,7 +161,7 @@ void ImmutableValidator::analyseVariableReference(VariableDeclaration const& _va
if (!_variableReference.isStateVariable() || !_variableReference.immutable())
return;
if (_expression.annotation().lValueRequested && _expression.annotation().lValueOfOrdinaryAssignment)
if (_expression.annotation().willBeWrittenTo && _expression.annotation().lValueOfOrdinaryAssignment)
{
if (!m_currentConstructor)
m_errorReporter.typeError(

View File

@ -195,51 +195,6 @@ Declaration const* NameAndTypeResolver::pathFromCurrentScope(vector<ASTString> c
return nullptr;
}
vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
Identifier const& _identifier,
vector<Declaration const*> const& _declarations
)
{
solAssert(_declarations.size() > 1, "");
vector<Declaration const*> uniqueFunctions;
for (Declaration const* declaration: _declarations)
{
solAssert(declaration, "");
// the declaration is functionDefinition, eventDefinition or a VariableDeclaration while declarations > 1
solAssert(
dynamic_cast<FunctionDefinition const*>(declaration) ||
dynamic_cast<EventDefinition const*>(declaration) ||
dynamic_cast<VariableDeclaration const*>(declaration) ||
dynamic_cast<MagicVariableDeclaration const*>(declaration),
"Found overloading involving something not a function, event or a (magic) variable."
);
FunctionTypePointer functionType { declaration->functionType(false) };
if (!functionType)
functionType = declaration->functionType(true);
solAssert(functionType, "Failed to determine the function type of the overloaded.");
for (auto parameter: functionType->parameterTypes() + functionType->returnParameterTypes())
if (!parameter)
m_errorReporter.fatalDeclarationError(_identifier.location(), "Function type can not be used in this context.");
if (uniqueFunctions.end() == find_if(
uniqueFunctions.begin(),
uniqueFunctions.end(),
[&](Declaration const* d)
{
FunctionType const* newFunctionType = d->functionType(false);
if (!newFunctionType)
newFunctionType = d->functionType(true);
return newFunctionType && functionType->hasEqualParameterTypes(*newFunctionType);
}
))
uniqueFunctions.push_back(declaration);
}
return uniqueFunctions;
}
void NameAndTypeResolver::warnVariablesNamedLikeInstructions()
{
for (auto const& instruction: evmasm::c_instructions)

View File

@ -95,12 +95,6 @@ public:
/// @note Returns a null pointer if any component in the path was not unique or not found.
Declaration const* pathFromCurrentScope(std::vector<ASTString> const& _path) const;
/// returns the vector of declarations without repetitions
std::vector<Declaration const*> cleanedDeclarations(
Identifier const& _identifier,
std::vector<Declaration const*> const& _declarations
);
/// Generate and store warnings about variables that are named like instructions.
void warnVariablesNamedLikeInstructions();

View File

@ -327,6 +327,16 @@ ModifierType const* OverrideProxy::modifierType() const
}, m_item);
}
Declaration const* OverrideProxy::declaration() const
{
return std::visit(GenericVisitor{
[&](FunctionDefinition const* _function) -> Declaration const* { return _function; },
[&](VariableDeclaration const* _variable) -> Declaration const* { return _variable; },
[&](ModifierDefinition const* _modifier) -> Declaration const* { return _modifier; }
}, m_item);
}
SourceLocation const& OverrideProxy::location() const
{
return std::visit(GenericVisitor{
@ -365,7 +375,7 @@ bool OverrideProxy::unimplemented() const
{
return std::visit(GenericVisitor{
[&](FunctionDefinition const* _item) { return !_item->isImplemented(); },
[&](ModifierDefinition const*) { return false; },
[&](ModifierDefinition const* _item) { return !_item->isImplemented(); },
[&](VariableDeclaration const*) { return false; }
}, m_item);
}

View File

@ -85,6 +85,8 @@ public:
FunctionType const* functionType() const;
ModifierType const* modifierType() const;
Declaration const* declaration() const;
langutil::SourceLocation const& location() const;
std::string astNodeName() const;

View File

@ -22,9 +22,7 @@
#include <libsolidity/analysis/ReferencesResolver.h>
#include <libsolidity/analysis/NameAndTypeResolver.h>
#include <libsolidity/analysis/ConstantEvaluator.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libyul/AsmAnalysis.h>
#include <libyul/AsmAnalysisInfo.h>
@ -37,7 +35,6 @@
#include <libsolutil/StringUtils.h>
#include <boost/algorithm/string.hpp>
#include <boost/range/adaptor/transformed.hpp>
using namespace std;
using namespace solidity::langutil;
@ -126,40 +123,10 @@ bool ReferencesResolver::visit(Identifier const& _identifier)
else if (declarations.size() == 1)
_identifier.annotation().referencedDeclaration = declarations.front();
else
_identifier.annotation().overloadedDeclarations =
m_resolver.cleanedDeclarations(_identifier, declarations);
_identifier.annotation().candidateDeclarations = declarations;
return false;
}
bool ReferencesResolver::visit(ElementaryTypeName const& _typeName)
{
if (!_typeName.annotation().type)
{
_typeName.annotation().type = TypeProvider::fromElementaryTypeName(_typeName.typeName());
if (_typeName.stateMutability().has_value())
{
// for non-address types this was already caught by the parser
solAssert(_typeName.annotation().type->category() == Type::Category::Address, "");
switch (*_typeName.stateMutability())
{
case StateMutability::Payable:
_typeName.annotation().type = TypeProvider::payableAddress();
break;
case StateMutability::NonPayable:
_typeName.annotation().type = TypeProvider::address();
break;
default:
m_errorReporter.typeError(
_typeName.location(),
"Address types can only be payable or non-payable."
);
break;
}
}
}
return true;
}
bool ReferencesResolver::visit(FunctionDefinition const& _functionDefinition)
{
m_returnParameters.push_back(_functionDefinition.returnParameterList().get());
@ -194,113 +161,6 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName)
}
_typeName.annotation().referencedDeclaration = declaration;
if (StructDefinition const* structDef = dynamic_cast<StructDefinition const*>(declaration))
_typeName.annotation().type = TypeProvider::structType(*structDef, DataLocation::Storage);
else if (EnumDefinition const* enumDef = dynamic_cast<EnumDefinition const*>(declaration))
_typeName.annotation().type = TypeProvider::enumType(*enumDef);
else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration))
_typeName.annotation().type = TypeProvider::contract(*contract);
else
{
_typeName.annotation().type = TypeProvider::emptyTuple();
fatalTypeError(_typeName.location(), "Name has to refer to a struct, enum or contract.");
}
}
void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
{
switch (_typeName.visibility())
{
case Visibility::Internal:
case Visibility::External:
break;
default:
fatalTypeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\".");
return;
}
if (_typeName.isPayable() && _typeName.visibility() != Visibility::External)
{
fatalTypeError(_typeName.location(), "Only external function types can be payable.");
return;
}
if (_typeName.visibility() == Visibility::External)
for (auto const& t: _typeName.parameterTypes() + _typeName.returnParameterTypes())
{
solAssert(t->annotation().type, "Type not set for parameter.");
if (!t->annotation().type->interfaceType(false).get())
{
fatalTypeError(t->location(), "Internal type cannot be used for external function type.");
return;
}
}
_typeName.annotation().type = TypeProvider::function(_typeName);
}
void ReferencesResolver::endVisit(Mapping const& _mapping)
{
if (auto const* typeName = dynamic_cast<UserDefinedTypeName const*>(&_mapping.keyType()))
{
if (auto const* contractType = dynamic_cast<ContractType const*>(typeName->annotation().type))
{
if (contractType->contractDefinition().isLibrary())
m_errorReporter.fatalTypeError(
typeName->location(),
"Library types cannot be used as mapping keys."
);
}
else if (typeName->annotation().type->category() != Type::Category::Enum)
m_errorReporter.fatalTypeError(
typeName->location(),
"Only elementary types, contract types or enums are allowed as mapping keys."
);
}
else
solAssert(dynamic_cast<ElementaryTypeName const*>(&_mapping.keyType()), "");
TypePointer keyType = _mapping.keyType().annotation().type;
TypePointer valueType = _mapping.valueType().annotation().type;
// Convert key type to memory.
keyType = TypeProvider::withLocationIfReference(DataLocation::Memory, keyType);
// Convert value type to storage reference.
valueType = TypeProvider::withLocationIfReference(DataLocation::Storage, valueType);
_mapping.annotation().type = TypeProvider::mapping(keyType, valueType);
}
void ReferencesResolver::endVisit(ArrayTypeName const& _typeName)
{
TypePointer baseType = _typeName.baseType().annotation().type;
if (!baseType)
{
solAssert(!m_errorReporter.errors().empty(), "");
return;
}
if (baseType->storageBytes() == 0)
fatalTypeError(_typeName.baseType().location(), "Illegal base type of storage size zero for array.");
if (Expression const* length = _typeName.length())
{
TypePointer& lengthTypeGeneric = length->annotation().type;
if (!lengthTypeGeneric)
lengthTypeGeneric = ConstantEvaluator(m_errorReporter).evaluate(*length);
RationalNumberType const* lengthType = dynamic_cast<RationalNumberType const*>(lengthTypeGeneric);
if (!lengthType || !lengthType->mobileType())
fatalTypeError(length->location(), "Invalid array length, expected integer literal or constant expression.");
else if (lengthType->isZero())
fatalTypeError(length->location(), "Array with zero length specified.");
else if (lengthType->isFractional())
fatalTypeError(length->location(), "Array with fractional length specified.");
else if (lengthType->isNegative())
fatalTypeError(length->location(), "Array with negative length specified.");
else
_typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType, lengthType->literalValue(nullptr));
}
else
_typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType);
}
bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
@ -321,115 +181,6 @@ bool ReferencesResolver::visit(Return const& _return)
return true;
}
void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
{
if (_variable.annotation().type)
return;
if (_variable.isConstant() && !_variable.isStateVariable())
m_errorReporter.declarationError(_variable.location(), "The \"constant\" keyword can only be used for state variables.");
if (_variable.immutable() && !_variable.isStateVariable())
m_errorReporter.declarationError(_variable.location(), "The \"immutable\" keyword can only be used for state variables.");
if (!_variable.typeName())
{
// This can still happen in very unusual cases where a developer uses constructs, such as
// `var a;`, however, such code will have generated errors already.
// However, we cannot blindingly solAssert() for that here, as the TypeChecker (which is
// invoking ReferencesResolver) is generating it, so the error is most likely(!) generated
// after this step.
return;
}
using Location = VariableDeclaration::Location;
Location varLoc = _variable.referenceLocation();
DataLocation typeLoc = DataLocation::Memory;
set<Location> allowedDataLocations = _variable.allowedDataLocations();
if (!allowedDataLocations.count(varLoc))
{
auto locationToString = [](VariableDeclaration::Location _location) -> string
{
switch (_location)
{
case Location::Memory: return "\"memory\"";
case Location::Storage: return "\"storage\"";
case Location::CallData: return "\"calldata\"";
case Location::Unspecified: return "none";
}
return {};
};
string errorString;
if (!_variable.hasReferenceOrMappingType())
errorString = "Data location can only be specified for array, struct or mapping types";
else
{
errorString = "Data location must be " +
util::joinHumanReadable(
allowedDataLocations | boost::adaptors::transformed(locationToString),
", ",
" or "
);
if (_variable.isCallableOrCatchParameter())
errorString +=
" for " +
string(_variable.isReturnParameter() ? "return " : "") +
"parameter in" +
string(_variable.isExternalCallableParameter() ? " external" : "") +
" function";
else
errorString += " for variable";
}
errorString += ", but " + locationToString(varLoc) + " was given.";
typeError(_variable.location(), errorString);
solAssert(!allowedDataLocations.empty(), "");
varLoc = *allowedDataLocations.begin();
}
// Find correct data location.
if (_variable.isEventParameter())
{
solAssert(varLoc == Location::Unspecified, "");
typeLoc = DataLocation::Memory;
}
else if (_variable.isStateVariable())
{
solAssert(varLoc == Location::Unspecified, "");
typeLoc = (_variable.isConstant() || _variable.immutable()) ? DataLocation::Memory : DataLocation::Storage;
}
else if (
dynamic_cast<StructDefinition const*>(_variable.scope()) ||
dynamic_cast<EnumDefinition const*>(_variable.scope())
)
// The actual location will later be changed depending on how the type is used.
typeLoc = DataLocation::Storage;
else
switch (varLoc)
{
case Location::Memory:
typeLoc = DataLocation::Memory;
break;
case Location::Storage:
typeLoc = DataLocation::Storage;
break;
case Location::CallData:
typeLoc = DataLocation::CallData;
break;
case Location::Unspecified:
solAssert(!_variable.hasReferenceOrMappingType(), "Data location not properly set.");
}
TypePointer type = _variable.typeName()->annotation().type;
if (auto ref = dynamic_cast<ReferenceType const*>(type))
{
bool isPointer = !_variable.isStateVariable();
type = TypeProvider::withLocation(ref, typeLoc, isPointer);
}
_variable.annotation().type = type;
}
void ReferencesResolver::operator()(yul::FunctionDefinition const& _function)
{
bool wasInsideFunction = m_yulInsideFunction;
@ -514,18 +265,6 @@ void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl)
visit(*_varDecl.value);
}
void ReferencesResolver::typeError(SourceLocation const& _location, string const& _description)
{
m_errorOccurred = true;
m_errorReporter.typeError(_location, _description);
}
void ReferencesResolver::fatalTypeError(SourceLocation const& _location, string const& _description)
{
m_errorOccurred = true;
m_errorReporter.fatalTypeError(_location, _description);
}
void ReferencesResolver::declarationError(SourceLocation const& _location, string const& _description)
{
m_errorOccurred = true;

View File

@ -76,29 +76,18 @@ private:
void endVisit(ForStatement const& _for) override;
void endVisit(VariableDeclarationStatement const& _varDeclStatement) override;
bool visit(Identifier const& _identifier) override;
bool visit(ElementaryTypeName const& _typeName) override;
bool visit(FunctionDefinition const& _functionDefinition) override;
void endVisit(FunctionDefinition const& _functionDefinition) override;
bool visit(ModifierDefinition const& _modifierDefinition) override;
void endVisit(ModifierDefinition const& _modifierDefinition) override;
void endVisit(UserDefinedTypeName const& _typeName) override;
void endVisit(FunctionTypeName const& _typeName) override;
void endVisit(Mapping const& _mapping) override;
void endVisit(ArrayTypeName const& _typeName) override;
bool visit(InlineAssembly const& _inlineAssembly) override;
bool visit(Return const& _return) override;
void endVisit(VariableDeclaration const& _variable) override;
void operator()(yul::FunctionDefinition const& _function) override;
void operator()(yul::Identifier const& _identifier) override;
void operator()(yul::VariableDeclaration const& _varDecl) override;
/// Adds a new error to the list of errors.
void typeError(langutil::SourceLocation const& _location, std::string const& _description);
/// Adds a new error to the list of errors and throws to abort reference resolving.
void fatalTypeError(langutil::SourceLocation const& _location, std::string const& _description);
/// Adds a new error to the list of errors.
void declarationError(langutil::SourceLocation const& _location, std::string const& _description);

View File

@ -141,7 +141,7 @@ bool SyntaxChecker::visit(ModifierDefinition const&)
void SyntaxChecker::endVisit(ModifierDefinition const& _modifier)
{
if (!m_placeholderFound)
if (_modifier.isImplemented() && !m_placeholderFound)
m_errorReporter.syntaxError(_modifier.body().location(), "Modifier body does not contain '_'.");
m_placeholderFound = false;
}

View File

@ -289,37 +289,10 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
m_errorReporter.fatalTypeError(_usingFor.libraryName().location(), "Library name expected.");
}
bool TypeChecker::visit(StructDefinition const& _struct)
void TypeChecker::endVisit(ModifierDefinition const& _modifier)
{
for (ASTPointer<VariableDeclaration> const& member: _struct.members())
solAssert(type(*member)->canBeStored(), "Type cannot be used in struct.");
// Check recursion, fatal error if detected.
auto visitor = [&](StructDefinition const& _struct, CycleDetector<StructDefinition>& _cycleDetector, size_t _depth)
{
if (_depth >= 256)
m_errorReporter.fatalDeclarationError(_struct.location(), "Struct definition exhausting cyclic dependency validator.");
for (ASTPointer<VariableDeclaration> const& member: _struct.members())
{
Type const* memberType = type(*member);
while (auto arrayType = dynamic_cast<ArrayType const*>(memberType))
{
if (arrayType->isDynamicallySized())
break;
memberType = arrayType->baseType();
}
if (auto structType = dynamic_cast<StructType const*>(memberType))
if (_cycleDetector.run(structType->structDefinition()))
return;
}
};
if (CycleDetector<StructDefinition>(visitor).run(_struct) != nullptr)
m_errorReporter.fatalTypeError(_struct.location(), "Recursive struct definition.");
ASTNode::listAccept(_struct.members(), *this);
return false;
if (!_modifier.isImplemented() && !_modifier.virtualSemantics())
m_errorReporter.typeError(_modifier.location(), "Modifiers without implementation must be marked virtual.");
}
bool TypeChecker::visit(FunctionDefinition const& _function)
@ -518,19 +491,16 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
m_errorReporter.typeError(_variable.location(), "Internal or recursive type is not allowed for public state variables.");
}
switch (varType->category())
if (auto referenceType = dynamic_cast<ReferenceType const*>(varType))
{
case Type::Category::Array:
if (auto arrayType = dynamic_cast<ArrayType const*>(varType))
if (
((arrayType->location() == DataLocation::Memory) ||
(arrayType->location() == DataLocation::CallData)) &&
!arrayType->validForCalldata()
)
m_errorReporter.typeError(_variable.location(), "Array is too large to be encoded.");
break;
default:
break;
auto result = referenceType->validForLocation(referenceType->location());
if (result && _variable.isPublicCallableParameter())
result = referenceType->validForLocation(DataLocation::CallData);
if (!result)
{
solAssert(!result.message().empty(), "Expected detailed error message");
m_errorReporter.typeError(_variable.location(), result.message());
}
}
return false;
@ -631,7 +601,15 @@ void TypeChecker::endVisit(FunctionTypeName const& _funType)
{
FunctionType const& fun = dynamic_cast<FunctionType const&>(*_funType.annotation().type);
if (fun.kind() == FunctionType::Kind::External)
{
for (auto const& t: _funType.parameterTypes() + _funType.returnParameterTypes())
{
solAssert(t->annotation().type, "Type not set for parameter.");
if (!t->annotation().type->interfaceType(false).get())
m_errorReporter.typeError(t->location(), "Internal type cannot be used for external function type.");
}
solAssert(fun.interfaceType(false), "External function type uses internal types.");
}
}
bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
@ -1303,7 +1281,7 @@ bool TypeChecker::visit(Conditional const& _conditional)
_conditional.trueExpression().annotation().isPure &&
_conditional.falseExpression().annotation().isPure;
if (_conditional.annotation().lValueRequested)
if (_conditional.annotation().willBeWrittenTo)
m_errorReporter.typeError(
_conditional.location(),
"Conditional expression as left value is not supported yet."
@ -1316,8 +1294,11 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const&
{
if (auto const* tupleExpression = dynamic_cast<TupleExpression const*>(&_expression))
{
if (tupleExpression->components().empty())
m_errorReporter.typeError(_expression.location(), "Empty tuple on the left hand side.");
auto const* tupleType = dynamic_cast<TupleType const*>(&_type);
auto const& types = tupleType && tupleExpression->components().size() > 1 ? tupleType->components() : vector<TypePointer> { &_type };
auto const& types = tupleType && tupleExpression->components().size() != 1 ? tupleType->components() : vector<TypePointer> { &_type };
solAssert(
tupleExpression->components().size() == types.size() || m_errorReporter.hasErrors(),
@ -1399,7 +1380,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
vector<ASTPointer<Expression>> const& components = _tuple.components();
TypePointers types;
if (_tuple.annotation().lValueRequested)
if (_tuple.annotation().willBeWrittenTo)
{
if (_tuple.isInlineArray())
m_errorReporter.fatalTypeError(_tuple.location(), "Inline array type cannot be declared as LValue.");
@ -1497,13 +1478,13 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
TypePointer t = type(_operation.subExpression())->unaryOperatorResult(op);
if (!t)
{
m_errorReporter.typeError(
_operation.location(),
"Unary operator " +
string(TokenTraits::toString(op)) +
" cannot be applied to type " +
subExprType->toString()
);
string description = "Unary operator " + string(TokenTraits::toString(op)) + " cannot be applied to type " + subExprType->toString();
if (modifying)
// Cannot just report the error, ignore the unary operator, and continue,
// because the sub-expression was already processed with requireLValue()
m_errorReporter.fatalTypeError(_operation.location(), description);
else
m_errorReporter.typeError(_operation.location(), description);
t = subExprType;
}
_operation.annotation().type = t;
@ -1796,6 +1777,10 @@ void TypeChecker::typeCheckReceiveFunction(FunctionDefinition const& _function)
void TypeChecker::typeCheckConstructor(FunctionDefinition const& _function)
{
solAssert(_function.isConstructor(), "");
if (_function.markedVirtual())
m_errorReporter.typeError(_function.location(), "Constructors cannot be virtual.");
if (_function.overrides())
m_errorReporter.typeError(_function.location(), "Constructors cannot override.");
if (!_function.returnParameters().empty())
m_errorReporter.typeError(_function.returnParameterList()->location(), "Non-empty \"returns\" directive for constructor.");
if (_function.stateMutability() != StateMutability::NonPayable && _function.stateMutability() != StateMutability::Payable)
@ -2613,6 +2598,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
}
else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "name")
annotation.isPure = true;
else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "interfaceId")
annotation.isPure = true;
}
return false;
@ -2776,11 +2763,57 @@ bool TypeChecker::visit(IndexRangeAccess const& _access)
return false;
}
vector<Declaration const*> TypeChecker::cleanOverloadedDeclarations(
Identifier const& _identifier,
vector<Declaration const*> const& _candidates
)
{
solAssert(_candidates.size() > 1, "");
vector<Declaration const*> uniqueDeclarations;
for (Declaration const* declaration: _candidates)
{
solAssert(declaration, "");
// the declaration is functionDefinition, eventDefinition or a VariableDeclaration while declarations > 1
solAssert(
dynamic_cast<FunctionDefinition const*>(declaration) ||
dynamic_cast<EventDefinition const*>(declaration) ||
dynamic_cast<VariableDeclaration const*>(declaration) ||
dynamic_cast<MagicVariableDeclaration const*>(declaration),
"Found overloading involving something not a function, event or a (magic) variable."
);
FunctionTypePointer functionType {declaration->functionType(false)};
if (!functionType)
functionType = declaration->functionType(true);
solAssert(functionType, "Failed to determine the function type of the overloaded.");
for (TypePointer parameter: functionType->parameterTypes() + functionType->returnParameterTypes())
if (!parameter)
m_errorReporter.fatalDeclarationError(_identifier.location(), "Function type can not be used in this context.");
if (uniqueDeclarations.end() == find_if(
uniqueDeclarations.begin(),
uniqueDeclarations.end(),
[&](Declaration const* d)
{
FunctionType const* newFunctionType = d->functionType(false);
if (!newFunctionType)
newFunctionType = d->functionType(true);
return newFunctionType && functionType->hasEqualParameterTypes(*newFunctionType);
}
))
uniqueDeclarations.push_back(declaration);
}
return uniqueDeclarations;
}
bool TypeChecker::visit(Identifier const& _identifier)
{
IdentifierAnnotation& annotation = _identifier.annotation();
if (!annotation.referencedDeclaration)
{
annotation.overloadedDeclarations = cleanOverloadedDeclarations(_identifier, annotation.candidateDeclarations);
if (!annotation.arguments)
{
// The identifier should be a public state variable shadowing other functions
@ -3000,7 +3033,7 @@ bool TypeChecker::expectType(Expression const& _expression, Type const& _expecte
void TypeChecker::requireLValue(Expression const& _expression, bool _ordinaryAssignment)
{
_expression.annotation().lValueRequested = true;
_expression.annotation().willBeWrittenTo = true;
_expression.annotation().lValueOfOrdinaryAssignment = _ordinaryAssignment;
_expression.accept(*this);

View File

@ -112,7 +112,7 @@ private:
void endVisit(InheritanceSpecifier const& _inheritance) override;
void endVisit(UsingForDirective const& _usingFor) override;
bool visit(StructDefinition const& _struct) override;
void endVisit(ModifierDefinition const& _modifier) override;
bool visit(FunctionDefinition const& _function) override;
bool visit(VariableDeclaration const& _variable) override;
/// We need to do this manually because we want to pass the bases of the current contract in
@ -154,6 +154,11 @@ private:
/// @returns the referenced declaration and throws on error.
Declaration const& dereference(UserDefinedTypeName const& _typeName) const;
std::vector<Declaration const*> cleanOverloadedDeclarations(
Identifier const& _reference,
std::vector<Declaration const*> const& _candidates
);
/// Runs type checks on @a _expression to infer its type and then checks that it is implicitly
/// convertible to @a _expectedType.
bool expectType(Expression const& _expression, Type const& _expectedType);

View File

@ -23,6 +23,7 @@
#include <libevmasm/SemanticInformation.h>
#include <functional>
#include <utility>
#include <variant>
using namespace std;
@ -41,7 +42,7 @@ public:
std::function<void(StateMutability, SourceLocation const&)> _reportMutability
):
m_dialect(_dialect),
m_reportMutability(_reportMutability) {}
m_reportMutability(std::move(_reportMutability)) {}
void operator()(yul::Literal const&) {}
void operator()(yul::Identifier const&) {}
@ -196,7 +197,7 @@ void ViewPureChecker::endVisit(Identifier const& _identifier)
StateMutability mutability = StateMutability::Pure;
bool writes = _identifier.annotation().lValueRequested;
bool writes = _identifier.annotation().willBeWrittenTo;
if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
{
if (varDecl->isStateVariable() && !varDecl->isConstant())
@ -331,7 +332,7 @@ bool ViewPureChecker::visit(MemberAccess const& _memberAccess)
void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
{
StateMutability mutability = StateMutability::Pure;
bool writes = _memberAccess.annotation().lValueRequested;
bool writes = _memberAccess.annotation().willBeWrittenTo;
ASTString const& member = _memberAccess.memberName();
switch (_memberAccess.expression().annotation().type->category())
@ -355,6 +356,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
{MagicType::Kind::MetaType, "creationCode"},
{MagicType::Kind::MetaType, "runtimeCode"},
{MagicType::Kind::MetaType, "name"},
{MagicType::Kind::MetaType, "interfaceId"},
};
set<MagicMember> static const payableMembers{
{MagicType::Kind::Message, "value"}
@ -401,7 +403,7 @@ void ViewPureChecker::endVisit(IndexAccess const& _indexAccess)
solAssert(_indexAccess.annotation().type->category() == Type::Category::TypeType, "");
else
{
bool writes = _indexAccess.annotation().lValueRequested;
bool writes = _indexAccess.annotation().willBeWrittenTo;
if (_indexAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage))
reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexAccess.location());
}
@ -409,7 +411,7 @@ void ViewPureChecker::endVisit(IndexAccess const& _indexAccess)
void ViewPureChecker::endVisit(IndexRangeAccess const& _indexRangeAccess)
{
bool writes = _indexRangeAccess.annotation().lValueRequested;
bool writes = _indexRangeAccess.annotation().willBeWrittenTo;
if (_indexRangeAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage))
reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexRangeAccess.location());
}

View File

@ -28,16 +28,18 @@
#include <libsolutil/Keccak256.h>
#include <boost/algorithm/string.hpp>
#include <algorithm>
#include <functional>
#include <utility>
using namespace std;
using namespace solidity;
using namespace solidity::frontend;
ASTNode::ASTNode(int64_t _id, SourceLocation const& _location):
ASTNode::ASTNode(int64_t _id, SourceLocation _location):
m_id(_id),
m_location(_location)
m_location(std::move(_location))
{
}
@ -96,9 +98,9 @@ bool ContractDefinition::derivesFrom(ContractDefinition const& _base) const
return util::contains(annotation().linearizedBaseContracts, &_base);
}
map<util::FixedHash<4>, FunctionTypePointer> ContractDefinition::interfaceFunctions() const
map<util::FixedHash<4>, FunctionTypePointer> ContractDefinition::interfaceFunctions(bool _includeInheritedFunctions) const
{
auto exportedFunctionList = interfaceFunctionList();
auto exportedFunctionList = interfaceFunctionList(_includeInheritedFunctions);
map<util::FixedHash<4>, FunctionTypePointer> exportedFunctions;
for (auto const& it: exportedFunctionList)
@ -174,14 +176,16 @@ vector<EventDefinition const*> const& ContractDefinition::interfaceEvents() cons
return *m_interfaceEvents;
}
vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList() const
vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::interfaceFunctionList(bool _includeInheritedFunctions) const
{
if (!m_interfaceFunctionList)
if (!m_interfaceFunctionList[_includeInheritedFunctions])
{
set<string> signaturesSeen;
m_interfaceFunctionList = make_unique<vector<pair<util::FixedHash<4>, FunctionTypePointer>>>();
m_interfaceFunctionList[_includeInheritedFunctions] = make_unique<vector<pair<util::FixedHash<4>, FunctionTypePointer>>>();
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
{
if (_includeInheritedFunctions == false && contract != this)
continue;
vector<FunctionTypePointer> functions;
for (FunctionDefinition const* f: contract->definedFunctions())
if (f->isPartOfExternalInterface())
@ -199,12 +203,12 @@ vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition:
{
signaturesSeen.insert(functionSignature);
util::FixedHash<4> hash(util::keccak256(functionSignature));
m_interfaceFunctionList->emplace_back(hash, fun);
m_interfaceFunctionList[_includeInheritedFunctions]->emplace_back(hash, fun);
}
}
}
}
return *m_interfaceFunctionList;
return *m_interfaceFunctionList[_includeInheritedFunctions];
}
TypePointer ContractDefinition::type() const
@ -255,12 +259,13 @@ TypeNameAnnotation& TypeName::annotation() const
TypePointer StructDefinition::type() const
{
solAssert(annotation().recursive.has_value(), "Requested struct type before DeclarationTypeChecker.");
return TypeProvider::typeType(TypeProvider::structType(*this, DataLocation::Storage));
}
TypeDeclarationAnnotation& StructDefinition::annotation() const
StructDeclarationAnnotation& StructDefinition::annotation() const
{
return initAnnotation<TypeDeclarationAnnotation>();
return initAnnotation<StructDeclarationAnnotation>();
}
TypePointer EnumValue::type() const
@ -556,6 +561,18 @@ bool VariableDeclaration::isExternalCallableParameter() const
return false;
}
bool VariableDeclaration::isPublicCallableParameter() const
{
if (!isCallableOrCatchParameter())
return false;
if (auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()))
if (callable->visibility() == Visibility::Public)
return !isReturnParameter();
return false;
}
bool VariableDeclaration::isInternalCallableParameter() const
{
if (!isCallableOrCatchParameter())
@ -614,12 +631,20 @@ set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() c
else if (isLocalVariable())
{
solAssert(typeName(), "");
solAssert(typeName()->annotation().type, "Can only be called after reference resolution");
if (typeName()->annotation().type->category() == Type::Category::Mapping)
return set<Location>{ Location::Storage };
else
// TODO: add Location::Calldata once implemented for local variables.
return set<Location>{ Location::Memory, Location::Storage };
auto dataLocations = [](TypePointer _type, auto&& _recursion) -> set<Location> {
solAssert(_type, "Can only be called after reference resolution");
switch (_type->category())
{
case Type::Category::Array:
return _recursion(dynamic_cast<ArrayType const*>(_type)->baseType(), _recursion);
case Type::Category::Mapping:
return set<Location>{ Location::Storage };
default:
// TODO: add Location::Calldata once implemented for local variables.
return set<Location>{ Location::Memory, Location::Storage };
}
};
return dataLocations(typeName()->annotation().type, dataLocations);
}
else
// Struct members etc.
@ -756,3 +781,25 @@ string Literal::getChecksummedAddress() const
address.insert(address.begin(), 40 - address.size(), '0');
return util::getChecksummedAddress(address);
}
TryCatchClause const* TryStatement::successClause() const
{
solAssert(m_clauses.size() > 0, "");
return m_clauses[0].get();
}
TryCatchClause const* TryStatement::structuredClause() const
{
for (size_t i = 1; i < m_clauses.size(); ++i)
if (m_clauses[i]->errorName() == "Error")
return m_clauses[i].get();
return nullptr;
}
TryCatchClause const* TryStatement::fallbackClause() const
{
for (size_t i = 1; i < m_clauses.size(); ++i)
if (m_clauses[i]->errorName().empty())
return m_clauses[i].get();
return nullptr;
}

View File

@ -38,6 +38,7 @@
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace solidity::yul
@ -82,7 +83,7 @@ public:
using SourceLocation = langutil::SourceLocation;
explicit ASTNode(int64_t _id, SourceLocation const& _location);
explicit ASTNode(int64_t _id, SourceLocation _location);
virtual ~ASTNode() {}
/// @returns an identifier of this AST node that is unique for a single compilation run.
@ -155,8 +156,8 @@ std::vector<T const*> ASTNode::filteredNodes(std::vector<ASTPointer<ASTNode>> co
class SourceUnit: public ASTNode
{
public:
SourceUnit(int64_t _id, SourceLocation const& _location, std::vector<ASTPointer<ASTNode>> const& _nodes):
ASTNode(_id, _location), m_nodes(_nodes) {}
SourceUnit(int64_t _id, SourceLocation const& _location, std::vector<ASTPointer<ASTNode>> _nodes):
ASTNode(_id, _location), m_nodes(std::move(_nodes)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -224,10 +225,10 @@ public:
Declaration(
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> const& _name,
ASTPointer<ASTString> _name,
Visibility _visibility = Visibility::Default
):
ASTNode(_id, _location), m_name(_name), m_visibility(_visibility) {}
ASTNode(_id, _location), m_name(std::move(_name)), m_visibility(_visibility) {}
/// @returns the declared name.
ASTString const& name() const { return *m_name; }
@ -275,9 +276,9 @@ public:
PragmaDirective(
int64_t _id,
SourceLocation const& _location,
std::vector<Token> const& _tokens,
std::vector<ASTString> const& _literals
): ASTNode(_id, _location), m_tokens(_tokens), m_literals(_literals)
std::vector<Token> _tokens,
std::vector<ASTString> _literals
): ASTNode(_id, _location), m_tokens(std::move(_tokens)), m_literals(std::move(_literals))
{}
void accept(ASTVisitor& _visitor) override;
@ -318,12 +319,12 @@ public:
ImportDirective(
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> const& _path,
ASTPointer<ASTString> _path,
ASTPointer<ASTString> const& _unitAlias,
SymbolAliasList _symbolAliases
):
Declaration(_id, _location, _unitAlias),
m_path(_path),
m_path(std::move(_path)),
m_symbolAliases(move(_symbolAliases))
{ }
@ -372,8 +373,8 @@ public:
StructuredDocumentation(
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> const& _text
): ASTNode(_id, _location), m_text(_text)
ASTPointer<ASTString> _text
): ASTNode(_id, _location), m_text(std::move(_text))
{}
void accept(ASTVisitor& _visitor) override;
@ -394,7 +395,7 @@ class Documented
{
public:
virtual ~Documented() = default;
explicit Documented(ASTPointer<ASTString> const& _documentation): m_documentation(_documentation) {}
explicit Documented(ASTPointer<ASTString> _documentation): m_documentation(std::move(_documentation)) {}
/// @return A shared pointer of an ASTString.
/// Can contain a nullptr in which case indicates absence of documentation
@ -411,7 +412,7 @@ class StructurallyDocumented
{
public:
virtual ~StructurallyDocumented() = default;
explicit StructurallyDocumented(ASTPointer<StructuredDocumentation> const& _documentation): m_documentation(_documentation) {}
explicit StructurallyDocumented(ASTPointer<StructuredDocumentation> _documentation): m_documentation(std::move(_documentation)) {}
/// @return A shared pointer of a FormalDocumentation.
/// Can contain a nullptr in which case indicates absence of documentation
@ -453,15 +454,15 @@ public:
SourceLocation const& _location,
ASTPointer<ASTString> const& _name,
ASTPointer<StructuredDocumentation> const& _documentation,
std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts,
std::vector<ASTPointer<ASTNode>> const& _subNodes,
std::vector<ASTPointer<InheritanceSpecifier>> _baseContracts,
std::vector<ASTPointer<ASTNode>> _subNodes,
ContractKind _contractKind = ContractKind::Contract,
bool _abstract = false
):
Declaration(_id, _location, _name),
StructurallyDocumented(_documentation),
m_baseContracts(_baseContracts),
m_subNodes(_subNodes),
m_baseContracts(std::move(_baseContracts)),
m_subNodes(std::move(_subNodes)),
m_contractKind(_contractKind),
m_abstract(_abstract)
{}
@ -488,8 +489,8 @@ public:
/// @returns a map of canonical function signatures to FunctionDefinitions
/// as intended for use by the ABI.
std::map<util::FixedHash<4>, FunctionTypePointer> interfaceFunctions() const;
std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>> const& interfaceFunctionList() const;
std::map<util::FixedHash<4>, FunctionTypePointer> interfaceFunctions(bool _includeInheritedFunctions = true) const;
std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>> const& interfaceFunctionList(bool _includeInheritedFunctions = true) const;
/// @returns a list of all declarations in this contract
std::vector<Declaration const*> declarations() const { return filteredNodes<Declaration>(m_subNodes); }
@ -528,7 +529,7 @@ private:
ContractKind m_contractKind;
bool m_abstract{false};
mutable std::unique_ptr<std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList;
mutable std::unique_ptr<std::vector<std::pair<util::FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList[2];
mutable std::unique_ptr<std::vector<EventDefinition const*>> m_interfaceEvents;
};
@ -538,10 +539,10 @@ public:
InheritanceSpecifier(
int64_t _id,
SourceLocation const& _location,
ASTPointer<UserDefinedTypeName> const& _baseName,
ASTPointer<UserDefinedTypeName> _baseName,
std::unique_ptr<std::vector<ASTPointer<Expression>>> _arguments
):
ASTNode(_id, _location), m_baseName(_baseName), m_arguments(std::move(_arguments)) {}
ASTNode(_id, _location), m_baseName(std::move(_baseName)), m_arguments(std::move(_arguments)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -568,10 +569,10 @@ public:
UsingForDirective(
int64_t _id,
SourceLocation const& _location,
ASTPointer<UserDefinedTypeName> const& _libraryName,
ASTPointer<TypeName> const& _typeName
ASTPointer<UserDefinedTypeName> _libraryName,
ASTPointer<TypeName> _typeName
):
ASTNode(_id, _location), m_libraryName(_libraryName), m_typeName(_typeName) {}
ASTNode(_id, _location), m_libraryName(std::move(_libraryName)), m_typeName(std::move(_typeName)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -592,9 +593,9 @@ public:
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> const& _name,
std::vector<ASTPointer<VariableDeclaration>> const& _members
std::vector<ASTPointer<VariableDeclaration>> _members
):
Declaration(_id, _location, _name), m_members(_members) {}
Declaration(_id, _location, _name), m_members(std::move(_members)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -606,7 +607,7 @@ public:
bool isVisibleInDerivedContracts() const override { return true; }
bool isVisibleViaContractTypeAccess() const override { return true; }
TypeDeclarationAnnotation& annotation() const override;
StructDeclarationAnnotation& annotation() const override;
private:
std::vector<ASTPointer<VariableDeclaration>> m_members;
@ -619,9 +620,9 @@ public:
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> const& _name,
std::vector<ASTPointer<EnumValue>> const& _members
std::vector<ASTPointer<EnumValue>> _members
):
Declaration(_id, _location, _name), m_members(_members) {}
Declaration(_id, _location, _name), m_members(std::move(_members)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -664,9 +665,9 @@ public:
ParameterList(
int64_t _id,
SourceLocation const& _location,
std::vector<ASTPointer<VariableDeclaration>> const& _parameters
std::vector<ASTPointer<VariableDeclaration>> _parameters
):
ASTNode(_id, _location), m_parameters(_parameters) {}
ASTNode(_id, _location), m_parameters(std::move(_parameters)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -688,15 +689,15 @@ public:
SourceLocation const& _location,
ASTPointer<ASTString> const& _name,
Visibility _visibility,
ASTPointer<ParameterList> const& _parameters,
ASTPointer<ParameterList> _parameters,
bool _isVirtual = false,
ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
ASTPointer<ParameterList> const& _returnParameters = ASTPointer<ParameterList>()
ASTPointer<OverrideSpecifier> _overrides = nullptr,
ASTPointer<ParameterList> _returnParameters = ASTPointer<ParameterList>()
):
Declaration(_id, _location, _name, _visibility),
m_parameters(_parameters),
m_overrides(_overrides),
m_returnParameters(_returnParameters),
m_parameters(std::move(_parameters)),
m_overrides(std::move(_overrides)),
m_returnParameters(std::move(_returnParameters)),
m_isVirtual(_isVirtual)
{
}
@ -740,10 +741,10 @@ public:
OverrideSpecifier(
int64_t _id,
SourceLocation const& _location,
std::vector<ASTPointer<UserDefinedTypeName>> const& _overrides
std::vector<ASTPointer<UserDefinedTypeName>> _overrides
):
ASTNode(_id, _location),
m_overrides(_overrides)
m_overrides(std::move(_overrides))
{
}
@ -771,7 +772,7 @@ public:
ASTPointer<OverrideSpecifier> const& _overrides,
ASTPointer<StructuredDocumentation> const& _documentation,
ASTPointer<ParameterList> const& _parameters,
std::vector<ASTPointer<ModifierInvocation>> const& _modifiers,
std::vector<ASTPointer<ModifierInvocation>> _modifiers,
ASTPointer<ParameterList> const& _returnParameters,
ASTPointer<Block> const& _body
):
@ -780,7 +781,7 @@ public:
ImplementationOptional(_body != nullptr),
m_stateMutability(_stateMutability),
m_kind(_kind),
m_functionModifiers(_modifiers),
m_functionModifiers(std::move(_modifiers)),
m_body(_body)
{
solAssert(_kind == Token::Constructor || _kind == Token::Function || _kind == Token::Fallback || _kind == Token::Receive, "");
@ -869,23 +870,23 @@ public:
VariableDeclaration(
int64_t _id,
SourceLocation const& _location,
ASTPointer<TypeName> const& _type,
ASTPointer<TypeName> _type,
ASTPointer<ASTString> const& _name,
ASTPointer<Expression> _value,
Visibility _visibility,
bool _isStateVar = false,
bool _isIndexed = false,
Mutability _mutability = Mutability::Mutable,
ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
ASTPointer<OverrideSpecifier> _overrides = nullptr,
Location _referenceLocation = Location::Unspecified
):
Declaration(_id, _location, _name, _visibility),
m_typeName(_type),
m_value(_value),
m_typeName(std::move(_type)),
m_value(std::move(_value)),
m_isStateVariable(_isStateVar),
m_isIndexed(_isIndexed),
m_mutability(_mutability),
m_overrides(_overrides),
m_overrides(std::move(_overrides)),
m_location(_referenceLocation) {}
@ -913,6 +914,8 @@ public:
/// @returns true if this variable is a parameter (not return parameter) of an external function.
/// This excludes parameters of external function type names.
bool isExternalCallableParameter() const;
/// @returns true if this variable is a parameter (not return parameter) of a public function.
bool isPublicCallableParameter() const;
/// @returns true if this variable is a parameter or return parameter of an internal function
/// or a function type of internal visibility.
bool isInternalCallableParameter() const;
@ -966,7 +969,7 @@ private:
/**
* Definition of a function modifier.
*/
class ModifierDefinition: public CallableDeclaration, public StructurallyDocumented
class ModifierDefinition: public CallableDeclaration, public StructurallyDocumented, public ImplementationOptional
{
public:
ModifierDefinition(
@ -981,6 +984,7 @@ public:
):
CallableDeclaration(_id, _location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides),
StructurallyDocumented(_documentation),
ImplementationOptional(_body != nullptr),
m_body(_body)
{
}
@ -988,7 +992,7 @@ public:
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
Block const& body() const { return *m_body; }
Block const& body() const { solAssert(m_body, ""); return *m_body; }
TypePointer type() const override;
@ -1015,10 +1019,10 @@ public:
ModifierInvocation(
int64_t _id,
SourceLocation const& _location,
ASTPointer<Identifier> const& _name,
ASTPointer<Identifier> _name,
std::unique_ptr<std::vector<ASTPointer<Expression>>> _arguments
):
ASTNode(_id, _location), m_modifierName(_name), m_arguments(std::move(_arguments)) {}
ASTNode(_id, _location), m_modifierName(std::move(_name)), m_arguments(std::move(_arguments)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1159,8 +1163,8 @@ private:
class UserDefinedTypeName: public TypeName
{
public:
UserDefinedTypeName(int64_t _id, SourceLocation const& _location, std::vector<ASTString> const& _namePath):
TypeName(_id, _location), m_namePath(_namePath) {}
UserDefinedTypeName(int64_t _id, SourceLocation const& _location, std::vector<ASTString> _namePath):
TypeName(_id, _location), m_namePath(std::move(_namePath)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1181,12 +1185,12 @@ public:
FunctionTypeName(
int64_t _id,
SourceLocation const& _location,
ASTPointer<ParameterList> const& _parameterTypes,
ASTPointer<ParameterList> const& _returnTypes,
ASTPointer<ParameterList> _parameterTypes,
ASTPointer<ParameterList> _returnTypes,
Visibility _visibility,
StateMutability _stateMutability
):
TypeName(_id, _location), m_parameterTypes(_parameterTypes), m_returnTypes(_returnTypes),
TypeName(_id, _location), m_parameterTypes(std::move(_parameterTypes)), m_returnTypes(std::move(_returnTypes)),
m_visibility(_visibility), m_stateMutability(_stateMutability)
{}
void accept(ASTVisitor& _visitor) override;
@ -1220,10 +1224,10 @@ public:
Mapping(
int64_t _id,
SourceLocation const& _location,
ASTPointer<TypeName> const& _keyType,
ASTPointer<TypeName> const& _valueType
ASTPointer<TypeName> _keyType,
ASTPointer<TypeName> _valueType
):
TypeName(_id, _location), m_keyType(_keyType), m_valueType(_valueType) {}
TypeName(_id, _location), m_keyType(std::move(_keyType)), m_valueType(std::move(_valueType)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1244,10 +1248,10 @@ public:
ArrayTypeName(
int64_t _id,
SourceLocation const& _location,
ASTPointer<TypeName> const& _baseType,
ASTPointer<Expression> const& _length
ASTPointer<TypeName> _baseType,
ASTPointer<Expression> _length
):
TypeName(_id, _location), m_baseType(_baseType), m_length(_length) {}
TypeName(_id, _location), m_baseType(std::move(_baseType)), m_length(std::move(_length)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1291,9 +1295,9 @@ public:
SourceLocation const& _location,
ASTPointer<ASTString> const& _docString,
yul::Dialect const& _dialect,
std::shared_ptr<yul::Block> const& _operations
std::shared_ptr<yul::Block> _operations
):
Statement(_id, _location, _docString), m_dialect(_dialect), m_operations(_operations) {}
Statement(_id, _location, _docString), m_dialect(_dialect), m_operations(std::move(_operations)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1317,9 +1321,9 @@ public:
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> const& _docString,
std::vector<ASTPointer<Statement>> const& _statements
std::vector<ASTPointer<Statement>> _statements
):
Statement(_id, _location, _docString), m_statements(_statements) {}
Statement(_id, _location, _docString), m_statements(std::move(_statements)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1359,14 +1363,14 @@ public:
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> const& _docString,
ASTPointer<Expression> const& _condition,
ASTPointer<Statement> const& _trueBody,
ASTPointer<Statement> const& _falseBody
ASTPointer<Expression> _condition,
ASTPointer<Statement> _trueBody,
ASTPointer<Statement> _falseBody
):
Statement(_id, _location, _docString),
m_condition(_condition),
m_trueBody(_trueBody),
m_falseBody(_falseBody)
m_condition(std::move(_condition)),
m_trueBody(std::move(_trueBody)),
m_falseBody(std::move(_falseBody))
{}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1393,14 +1397,14 @@ public:
TryCatchClause(
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> const& _errorName,
ASTPointer<ParameterList> const& _parameters,
ASTPointer<Block> const& _block
ASTPointer<ASTString> _errorName,
ASTPointer<ParameterList> _parameters,
ASTPointer<Block> _block
):
ASTNode(_id, _location),
m_errorName(_errorName),
m_parameters(_parameters),
m_block(_block)
m_errorName(std::move(_errorName)),
m_parameters(std::move(_parameters)),
m_block(std::move(_block))
{}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1438,12 +1442,12 @@ public:
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> const& _docString,
ASTPointer<Expression> const& _externalCall,
std::vector<ASTPointer<TryCatchClause>> const& _clauses
ASTPointer<Expression> _externalCall,
std::vector<ASTPointer<TryCatchClause>> _clauses
):
Statement(_id, _location, _docString),
m_externalCall(_externalCall),
m_clauses(_clauses)
m_externalCall(std::move(_externalCall)),
m_clauses(std::move(_clauses))
{}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1451,6 +1455,10 @@ public:
Expression const& externalCall() const { return *m_externalCall; }
std::vector<ASTPointer<TryCatchClause>> const& clauses() const { return m_clauses; }
TryCatchClause const* successClause() const;
TryCatchClause const* structuredClause() const;
TryCatchClause const* fallbackClause() const;
private:
ASTPointer<Expression> m_externalCall;
std::vector<ASTPointer<TryCatchClause>> m_clauses;
@ -1476,11 +1484,11 @@ public:
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> const& _docString,
ASTPointer<Expression> const& _condition,
ASTPointer<Statement> const& _body,
ASTPointer<Expression> _condition,
ASTPointer<Statement> _body,
bool _isDoWhile
):
BreakableStatement(_id, _location, _docString), m_condition(_condition), m_body(_body),
BreakableStatement(_id, _location, _docString), m_condition(std::move(_condition)), m_body(std::move(_body)),
m_isDoWhile(_isDoWhile) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1505,16 +1513,16 @@ public:
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> const& _docString,
ASTPointer<Statement> const& _initExpression,
ASTPointer<Expression> const& _conditionExpression,
ASTPointer<ExpressionStatement> const& _loopExpression,
ASTPointer<Statement> const& _body
ASTPointer<Statement> _initExpression,
ASTPointer<Expression> _conditionExpression,
ASTPointer<ExpressionStatement> _loopExpression,
ASTPointer<Statement> _body
):
BreakableStatement(_id, _location, _docString),
m_initExpression(_initExpression),
m_condExpression(_conditionExpression),
m_loopExpression(_loopExpression),
m_body(_body)
m_initExpression(std::move(_initExpression)),
m_condExpression(std::move(_conditionExpression)),
m_loopExpression(std::move(_loopExpression)),
m_body(std::move(_body))
{}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1563,7 +1571,7 @@ public:
SourceLocation const& _location,
ASTPointer<ASTString> const& _docString,
ASTPointer<Expression> _expression
): Statement(_id, _location, _docString), m_expression(_expression) {}
): Statement(_id, _location, _docString), m_expression(std::move(_expression)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1597,9 +1605,9 @@ public:
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> const& _docString,
ASTPointer<FunctionCall> const& _functionCall
ASTPointer<FunctionCall> _functionCall
):
Statement(_id, _location, _docString), m_eventCall(_functionCall) {}
Statement(_id, _location, _docString), m_eventCall(std::move(_functionCall)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1624,10 +1632,10 @@ public:
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> const& _docString,
std::vector<ASTPointer<VariableDeclaration>> const& _variables,
ASTPointer<Expression> const& _initialValue
std::vector<ASTPointer<VariableDeclaration>> _variables,
ASTPointer<Expression> _initialValue
):
Statement(_id, _location, _docString), m_variables(_variables), m_initialValue(_initialValue) {}
Statement(_id, _location, _docString), m_variables(std::move(_variables)), m_initialValue(std::move(_initialValue)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1656,7 +1664,7 @@ public:
ASTPointer<ASTString> const& _docString,
ASTPointer<Expression> _expression
):
Statement(_id, _location, _docString), m_expression(_expression) {}
Statement(_id, _location, _docString), m_expression(std::move(_expression)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1690,14 +1698,14 @@ public:
Conditional(
int64_t _id,
SourceLocation const& _location,
ASTPointer<Expression> const& _condition,
ASTPointer<Expression> const& _trueExpression,
ASTPointer<Expression> const& _falseExpression
ASTPointer<Expression> _condition,
ASTPointer<Expression> _trueExpression,
ASTPointer<Expression> _falseExpression
):
Expression(_id, _location),
m_condition(_condition),
m_trueExpression(_trueExpression),
m_falseExpression(_falseExpression)
m_condition(std::move(_condition)),
m_trueExpression(std::move(_trueExpression)),
m_falseExpression(std::move(_falseExpression))
{}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1720,14 +1728,14 @@ public:
Assignment(
int64_t _id,
SourceLocation const& _location,
ASTPointer<Expression> const& _leftHandSide,
ASTPointer<Expression> _leftHandSide,
Token _assignmentOperator,
ASTPointer<Expression> const& _rightHandSide
ASTPointer<Expression> _rightHandSide
):
Expression(_id, _location),
m_leftHandSide(_leftHandSide),
m_leftHandSide(std::move(_leftHandSide)),
m_assigmentOperator(_assignmentOperator),
m_rightHandSide(_rightHandSide)
m_rightHandSide(std::move(_rightHandSide))
{
solAssert(TokenTraits::isAssignmentOp(_assignmentOperator), "");
}
@ -1758,11 +1766,11 @@ public:
TupleExpression(
int64_t _id,
SourceLocation const& _location,
std::vector<ASTPointer<Expression>> const& _components,
std::vector<ASTPointer<Expression>> _components,
bool _isArray
):
Expression(_id, _location),
m_components(_components),
m_components(std::move(_components)),
m_isArray(_isArray) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1786,12 +1794,12 @@ public:
int64_t _id,
SourceLocation const& _location,
Token _operator,
ASTPointer<Expression> const& _subExpression,
ASTPointer<Expression> _subExpression,
bool _isPrefix
):
Expression(_id, _location),
m_operator(_operator),
m_subExpression(_subExpression),
m_subExpression(std::move(_subExpression)),
m_isPrefix(_isPrefix)
{
solAssert(TokenTraits::isUnaryOp(_operator), "");
@ -1819,11 +1827,11 @@ public:
BinaryOperation(
int64_t _id,
SourceLocation const& _location,
ASTPointer<Expression> const& _left,
ASTPointer<Expression> _left,
Token _operator,
ASTPointer<Expression> const& _right
ASTPointer<Expression> _right
):
Expression(_id, _location), m_left(_left), m_operator(_operator), m_right(_right)
Expression(_id, _location), m_left(std::move(_left)), m_operator(_operator), m_right(std::move(_right))
{
solAssert(TokenTraits::isBinaryOp(_operator) || TokenTraits::isCompareOp(_operator), "");
}
@ -1851,11 +1859,11 @@ public:
FunctionCall(
int64_t _id,
SourceLocation const& _location,
ASTPointer<Expression> const& _expression,
std::vector<ASTPointer<Expression>> const& _arguments,
std::vector<ASTPointer<ASTString>> const& _names
ASTPointer<Expression> _expression,
std::vector<ASTPointer<Expression>> _arguments,
std::vector<ASTPointer<ASTString>> _names
):
Expression(_id, _location), m_expression(_expression), m_arguments(_arguments), m_names(_names) {}
Expression(_id, _location), m_expression(std::move(_expression)), m_arguments(std::move(_arguments)), m_names(std::move(_names)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1881,11 +1889,11 @@ public:
FunctionCallOptions(
int64_t _id,
SourceLocation const& _location,
ASTPointer<Expression> const& _expression,
std::vector<ASTPointer<Expression>> const& _options,
std::vector<ASTPointer<ASTString>> const& _names
ASTPointer<Expression> _expression,
std::vector<ASTPointer<Expression>> _options,
std::vector<ASTPointer<ASTString>> _names
):
Expression(_id, _location), m_expression(_expression), m_options(_options), m_names(_names) {}
Expression(_id, _location), m_expression(std::move(_expression)), m_options(std::move(_options)), m_names(std::move(_names)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1910,9 +1918,9 @@ public:
NewExpression(
int64_t _id,
SourceLocation const& _location,
ASTPointer<TypeName> const& _typeName
ASTPointer<TypeName> _typeName
):
Expression(_id, _location), m_typeName(_typeName) {}
Expression(_id, _location), m_typeName(std::move(_typeName)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1932,9 +1940,9 @@ public:
int64_t _id,
SourceLocation const& _location,
ASTPointer<Expression> _expression,
ASTPointer<ASTString> const& _memberName
ASTPointer<ASTString> _memberName
):
Expression(_id, _location), m_expression(_expression), m_memberName(_memberName) {}
Expression(_id, _location), m_expression(std::move(_expression)), m_memberName(std::move(_memberName)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
Expression const& expression() const { return *m_expression; }
@ -1956,10 +1964,10 @@ public:
IndexAccess(
int64_t _id,
SourceLocation const& _location,
ASTPointer<Expression> const& _base,
ASTPointer<Expression> const& _index
ASTPointer<Expression> _base,
ASTPointer<Expression> _index
):
Expression(_id, _location), m_base(_base), m_index(_index) {}
Expression(_id, _location), m_base(std::move(_base)), m_index(std::move(_index)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -1980,11 +1988,11 @@ public:
IndexRangeAccess(
int64_t _id,
SourceLocation const& _location,
ASTPointer<Expression> const& _base,
ASTPointer<Expression> const& _start,
ASTPointer<Expression> const& _end
ASTPointer<Expression> _base,
ASTPointer<Expression> _start,
ASTPointer<Expression> _end
):
Expression(_id, _location), m_base(_base), m_start(_start), m_end(_end) {}
Expression(_id, _location), m_base(std::move(_base)), m_start(std::move(_start)), m_end(std::move(_end)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -2017,9 +2025,9 @@ public:
Identifier(
int64_t _id,
SourceLocation const& _location,
ASTPointer<ASTString> const& _name
ASTPointer<ASTString> _name
):
PrimaryExpression(_id, _location), m_name(_name) {}
PrimaryExpression(_id, _location), m_name(std::move(_name)) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
@ -2042,10 +2050,10 @@ public:
ElementaryTypeNameExpression(
int64_t _id,
SourceLocation const& _location,
ASTPointer<ElementaryTypeName> const& _type
ASTPointer<ElementaryTypeName> _type
):
PrimaryExpression(_id, _location),
m_type(_type)
m_type(std::move(_type))
{
}
void accept(ASTVisitor& _visitor) override;
@ -2081,10 +2089,10 @@ public:
int64_t _id,
SourceLocation const& _location,
Token _token,
ASTPointer<ASTString> const& _value,
ASTPointer<ASTString> _value,
SubDenomination _sub = SubDenomination::None
):
PrimaryExpression(_id, _location), m_token(_token), m_value(_value), m_subDenomination(_sub) {}
PrimaryExpression(_id, _location), m_token(_token), m_value(std::move(_value)), m_subDenomination(_sub) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;

View File

@ -128,10 +128,20 @@ struct TypeDeclarationAnnotation: DeclarationAnnotation
std::string canonicalName;
};
struct StructDeclarationAnnotation: TypeDeclarationAnnotation
{
/// Whether the struct is recursive, i.e. if the struct (recursively) contains a member that involves a struct of the same
/// type, either in a dynamic array, as member of another struct or inside a mapping.
/// Only cases in which the recursive occurrence is within a dynamic array or a mapping are valid, while direct
/// recursion immediately raises an error.
/// Will be filled in by the DeclarationTypeChecker.
std::optional<bool> recursive;
};
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation
{
/// List of functions without a body. Can also contain functions from base classes.
std::vector<FunctionDefinition const*> unimplementedFunctions;
/// List of functions and modifiers without a body. Can also contain functions from base classes.
std::vector<Declaration const*> unimplementedDeclarations;
/// List of all (direct and indirect) base contracts in order from derived to
/// base, including the contract itself.
std::vector<ContractDefinition const*> linearizedBaseContracts;
@ -234,7 +244,7 @@ struct ExpressionAnnotation: ASTAnnotation
/// Whether it is an LValue (i.e. something that can be assigned to).
bool isLValue = false;
/// Whether the expression is used in a context where the LValue is actually required.
bool lValueRequested = false;
bool willBeWrittenTo = false;
/// Whether the expression is an lvalue that is only assigned.
/// Would be false for --, ++, delete, +=, -=, ....
bool lValueOfOrdinaryAssignment = false;
@ -248,6 +258,8 @@ struct IdentifierAnnotation: ExpressionAnnotation
{
/// Referenced declaration, set at latest during overload resolution stage.
Declaration const* referencedDeclaration = nullptr;
/// List of possible declarations it could refer to (can contain duplicates).
std::vector<Declaration const*> candidateDeclarations;
/// List of possible declarations it could refer to.
std::vector<Declaration const*> overloadedDeclarations;
};

View File

@ -34,6 +34,7 @@
#include <boost/algorithm/string/join.hpp>
#include <boost/range/algorithm/sort.hpp>
#include <utility>
#include <vector>
#include <algorithm>
@ -45,7 +46,7 @@ namespace solidity::frontend
ASTJsonConverter::ASTJsonConverter(bool _legacy, map<string, unsigned> _sourceIndices):
m_legacy(_legacy),
m_sourceIndices(_sourceIndices)
m_sourceIndices(std::move(_sourceIndices))
{
}
@ -181,7 +182,7 @@ void ASTJsonConverter::appendExpressionAttributes(
make_pair("isConstant", _annotation.isConstant),
make_pair("isPure", _annotation.isPure),
make_pair("isLValue", _annotation.isLValue),
make_pair("lValueRequested", _annotation.lValueRequested),
make_pair("lValueRequested", _annotation.willBeWrittenTo),
make_pair("argumentTypes", typePointerToJson(_annotation.arguments))
};
_attributes += exprAttributes;
@ -271,7 +272,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue),
make_pair("contractKind", contractKind(_node.contractKind())),
make_pair("abstract", _node.abstract()),
make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()),
make_pair("fullyImplemented", _node.annotation().unimplementedDeclarations.empty()),
make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)),
make_pair("baseContracts", toJson(_node.baseContracts())),
make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies, true)),
@ -406,7 +407,7 @@ bool ASTJsonConverter::visit(ModifierDefinition const& _node)
make_pair("parameters", toJson(_node.parameterList())),
make_pair("virtual", _node.markedVirtual()),
make_pair("overrides", _node.overrides() ? toJson(*_node.overrides()) : Json::nullValue),
make_pair("body", toJson(_node.body()))
make_pair("body", _node.isImplemented() ? toJson(_node.body()) : Json::nullValue)
};
if (!_node.annotation().baseFunctions.empty())
attributes.emplace_back(make_pair("baseModifiers", getContainerIds(_node.annotation().baseFunctions, true)));

View File

@ -453,7 +453,7 @@ ASTPointer<ModifierDefinition> ASTJsonImporter::createModifierDefinition(Json::V
createParameterList(member(_node, "parameters")),
memberAsBool(_node, "virtual"),
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
createBlock(member(_node, "body"))
_node["body"].isNull() ? nullptr: createBlock(member(_node, "body"))
);
}

View File

@ -23,9 +23,11 @@
#pragma once
#include <libsolidity/ast/AST.h>
#include <functional>
#include <string>
#include <vector>
#include <utility>
namespace solidity::frontend
{
@ -299,7 +301,7 @@ public:
SimpleASTVisitor(
std::function<bool(ASTNode const&)> _onVisit,
std::function<void(ASTNode const&)> _onEndVisit
): m_onVisit(_onVisit), m_onEndVisit(_onEndVisit) {}
): m_onVisit(std::move(_onVisit)), m_onEndVisit(std::move(_onEndVisit)) {}
protected:
bool visitNode(ASTNode const& _n) override { return m_onVisit ? m_onVisit(_n) : true; }
@ -329,7 +331,7 @@ public:
ASTReduce(
std::function<bool(ASTNode const&)> _onNode,
std::function<void(ASTNode const&, ASTNode const&)> _onEdge
): m_onNode(_onNode), m_onEdge(_onEdge)
): m_onNode(std::move(_onNode)), m_onEdge(std::move(_onEdge))
{
}

View File

@ -288,7 +288,8 @@ void ModifierDefinition::accept(ASTVisitor& _visitor)
m_parameters->accept(_visitor);
if (m_overrides)
m_overrides->accept(_visitor);
m_body->accept(_visitor);
if (m_body)
m_body->accept(_visitor);
}
_visitor.endVisit(*this);
}
@ -302,7 +303,8 @@ void ModifierDefinition::accept(ASTConstVisitor& _visitor) const
m_parameters->accept(_visitor);
if (m_overrides)
m_overrides->accept(_visitor);
m_body->accept(_visitor);
if (m_body)
m_body->accept(_visitor);
}
_visitor.endVisit(*this);
}

View File

@ -26,6 +26,8 @@
#include <liblangutil/SourceLocation.h>
#include <libyul/AsmDataForward.h>
#include <utility>
namespace solidity::frontend
{
@ -35,7 +37,7 @@ namespace solidity::frontend
class AsmJsonImporter
{
public:
explicit AsmJsonImporter(std::string _sourceName) : m_sourceName(_sourceName) {}
explicit AsmJsonImporter(std::string _sourceName) : m_sourceName(std::move(_sourceName)) {}
yul::Block createBlock(Json::Value const& _node);
private:

View File

@ -43,6 +43,7 @@
#include <boost/range/algorithm/copy.hpp>
#include <limits>
#include <utility>
using namespace std;
using namespace solidity;
@ -1253,8 +1254,8 @@ StringLiteralType::StringLiteralType(Literal const& _literal):
{
}
StringLiteralType::StringLiteralType(string const& _value):
m_value{_value}
StringLiteralType::StringLiteralType(string _value):
m_value{std::move(_value)}
{
}
@ -1648,12 +1649,50 @@ bool ArrayType::operator==(Type const& _other) const
return isDynamicallySized() || length() == other.length();
}
bool ArrayType::validForCalldata() const
BoolResult ArrayType::validForLocation(DataLocation _loc) const
{
if (auto arrayBaseType = dynamic_cast<ArrayType const*>(baseType()))
if (!arrayBaseType->validForCalldata())
return false;
return isDynamicallySized() || unlimitedStaticCalldataSize(true) <= numeric_limits<unsigned>::max();
{
BoolResult result = arrayBaseType->validForLocation(_loc);
if (!result)
return result;
}
if (isDynamicallySized())
return true;
switch (_loc)
{
case DataLocation::Memory:
{
bigint size = bigint(length());
auto type = m_baseType;
while (auto arrayType = dynamic_cast<ArrayType const*>(type))
{
if (arrayType->isDynamicallySized())
break;
else
{
size *= arrayType->length();
type = arrayType->baseType();
}
}
if (type->isDynamicallySized())
size *= type->memoryHeadSize();
else
size *= type->memoryDataSize();
if (size >= numeric_limits<unsigned>::max())
return BoolResult::err("Type too large for memory.");
break;
}
case DataLocation::CallData:
{
if (unlimitedStaticCalldataSize(true) >= numeric_limits<unsigned>::max())
return BoolResult::err("Type too large for calldata.");
break;
}
case DataLocation::Storage:
break;
}
return true;
}
bigint ArrayType::unlimitedStaticCalldataSize(bool _padded) const
@ -2174,93 +2213,119 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const
TypeResult StructType::interfaceType(bool _inLibrary) const
{
if (_inLibrary && m_interfaceType_library.has_value())
return *m_interfaceType_library;
if (!_inLibrary && m_interfaceType.has_value())
if (!_inLibrary)
{
if (!m_interfaceType.has_value())
{
if (recursive())
m_interfaceType = TypeResult::err("Recursive type not allowed for public or external contract functions.");
else
{
TypeResult result{TypePointer{}};
for (ASTPointer<VariableDeclaration> const& member: m_struct.members())
{
if (!member->annotation().type)
{
result = TypeResult::err("Invalid type!");
break;
}
auto interfaceType = member->annotation().type->interfaceType(false);
if (!interfaceType.get())
{
solAssert(!interfaceType.message().empty(), "Expected detailed error message!");
result = interfaceType;
break;
}
}
if (result.message().empty())
m_interfaceType = TypeProvider::withLocation(this, DataLocation::Memory, true);
else
m_interfaceType = result;
}
}
return *m_interfaceType;
}
else if (m_interfaceType_library.has_value())
return *m_interfaceType_library;
TypeResult result{TypePointer{}};
m_recursive = false;
auto visitor = [&](
StructDefinition const& _struct,
util::CycleDetector<StructDefinition>& _cycleDetector,
size_t /*_depth*/
)
{
// Check that all members have interface types.
// Return an error if at least one struct member does not have a type.
// This might happen, for example, if the type of the member does not exist.
for (ASTPointer<VariableDeclaration> const& variable: _struct.members())
{
// If the struct member does not have a type return false.
// A TypeError is expected in this case.
if (!variable->annotation().type)
{
result = TypeResult::err("Invalid type!");
return;
}
Type const* memberType = variable->annotation().type;
while (dynamic_cast<ArrayType const*>(memberType))
memberType = dynamic_cast<ArrayType const*>(memberType)->baseType();
if (StructType const* innerStruct = dynamic_cast<StructType const*>(memberType))
if (
innerStruct->m_recursive == true ||
_cycleDetector.run(innerStruct->structDefinition())
)
util::BreadthFirstSearch<StructDefinition const*> breadthFirstSearch{{&m_struct}};
breadthFirstSearch.run(
[&](StructDefinition const* _struct, auto&& _addChild) {
// Check that all members have interface types.
// Return an error if at least one struct member does not have a type.
// This might happen, for example, if the type of the member does not exist.
for (ASTPointer<VariableDeclaration> const& variable: _struct->members())
{
m_recursive = true;
if (_inLibrary && location() == DataLocation::Storage)
continue;
else
// If the struct member does not have a type return false.
// A TypeError is expected in this case.
if (!variable->annotation().type)
{
result = TypeResult::err("Recursive structs can only be passed as storage pointers to libraries, not as memory objects to contract functions.");
result = TypeResult::err("Invalid type!");
breadthFirstSearch.abort();
return;
}
}
auto iType = memberType->interfaceType(_inLibrary);
if (!iType.get())
{
solAssert(!iType.message().empty(), "Expected detailed error message!");
result = iType;
return;
Type const* memberType = variable->annotation().type;
while (dynamic_cast<ArrayType const*>(memberType))
memberType = dynamic_cast<ArrayType const*>(memberType)->baseType();
if (StructType const* innerStruct = dynamic_cast<StructType const*>(memberType))
{
if (innerStruct->recursive() && !(_inLibrary && location() == DataLocation::Storage))
{
result = TypeResult::err(
"Recursive structs can only be passed as storage pointers to libraries, not as memory objects to contract functions."
);
breadthFirstSearch.abort();
return;
}
else
_addChild(&innerStruct->structDefinition());
}
else
{
auto iType = memberType->interfaceType(_inLibrary);
if (!iType.get())
{
solAssert(!iType.message().empty(), "Expected detailed error message!");
result = iType;
breadthFirstSearch.abort();
return;
}
}
}
}
};
);
m_recursive = m_recursive.value() || (util::CycleDetector<StructDefinition>(visitor).run(structDefinition()) != nullptr);
if (!result.message().empty())
return result;
std::string const recursiveErrMsg = "Recursive type not allowed for public or external contract functions.";
if (_inLibrary)
{
if (!result.message().empty())
m_interfaceType_library = result;
else if (location() == DataLocation::Storage)
m_interfaceType_library = this;
else
m_interfaceType_library = TypeProvider::withLocation(this, DataLocation::Memory, true);
if (m_recursive.value())
m_interfaceType = TypeResult::err(recursiveErrMsg);
return *m_interfaceType_library;
}
if (m_recursive.value())
m_interfaceType = TypeResult::err(recursiveErrMsg);
else if (!result.message().empty())
m_interfaceType = result;
if (location() == DataLocation::Storage)
m_interfaceType_library = this;
else
m_interfaceType = TypeProvider::withLocation(this, DataLocation::Memory, true);
m_interfaceType_library = TypeProvider::withLocation(this, DataLocation::Memory, true);
return *m_interfaceType_library;
}
return *m_interfaceType;
BoolResult StructType::validForLocation(DataLocation _loc) const
{
for (auto const& member: m_struct.members())
if (auto referenceType = dynamic_cast<ReferenceType const*>(member->annotation().type))
{
BoolResult result = referenceType->validForLocation(_loc);
if (!result)
return result;
}
return true;
}
bool StructType::recursive() const
{
solAssert(m_struct.annotation().recursive.has_value(), "Called StructType::recursive() before DeclarationTypeChecker.");
return *m_struct.annotation().recursive;
}
std::unique_ptr<ReferenceType> StructType::copyForLocation(DataLocation _location, bool _isPointer) const
@ -2643,21 +2708,11 @@ FunctionType::FunctionType(FunctionTypeName const& _typeName):
for (auto const& t: _typeName.parameterTypes())
{
solAssert(t->annotation().type, "Type not set for parameter.");
if (m_kind == Kind::External)
solAssert(
t->annotation().type->interfaceType(false).get(),
"Internal type used as parameter for external function."
);
m_parameterTypes.push_back(t->annotation().type);
}
for (auto const& t: _typeName.returnParameterTypes())
{
solAssert(t->annotation().type, "Type not set for return parameter.");
if (m_kind == Kind::External)
solAssert(
t->annotation().type->interfaceType(false).get(),
"Internal type used as return parameter for external function."
);
m_returnParameterTypes.push_back(t->annotation().type);
}
@ -3719,7 +3774,9 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
{"name", TypeProvider::stringMemory()},
});
else
return {};
return MemberList::MemberMap({
{"interfaceId", TypeProvider::fixedBytes(4)},
});
}
}
solAssert(false, "Unknown kind of magic.");

View File

@ -38,6 +38,7 @@
#include <optional>
#include <set>
#include <string>
#include <utility>
namespace solidity::frontend
{
@ -92,8 +93,8 @@ class MemberList
public:
struct Member
{
Member(std::string const& _name, Type const* _type, Declaration const* _declaration = nullptr):
name(_name),
Member(std::string _name, Type const* _type, Declaration const* _declaration = nullptr):
name(std::move(_name)),
type(_type),
declaration(_declaration)
{
@ -106,7 +107,7 @@ public:
using MemberMap = std::vector<Member>;
explicit MemberList(MemberMap const& _members): m_memberTypes(_members) {}
explicit MemberList(MemberMap _members): m_memberTypes(std::move(_members)) {}
void combine(MemberList const& _other);
TypePointer memberType(std::string const& _name) const
@ -520,8 +521,8 @@ private:
class RationalNumberType: public Type
{
public:
explicit RationalNumberType(rational const& _value, Type const* _compatibleBytesType = nullptr):
m_value(_value), m_compatibleBytesType(_compatibleBytesType)
explicit RationalNumberType(rational _value, Type const* _compatibleBytesType = nullptr):
m_value(std::move(_value)), m_compatibleBytesType(_compatibleBytesType)
{}
Category category() const override { return Category::RationalNumber; }
@ -543,7 +544,7 @@ public:
/// @returns the smallest integer type that can hold the value or an empty pointer if not possible.
IntegerType const* integerType() const;
/// @returns the smallest fixed type that can hold the value or incurs the least precision loss,
/// @returns the smallest fixed type that can hold the value or incurs the least precision loss,
/// unless the value was truncated, then a suitable type will be chosen to indicate such event.
/// If the integer part does not fit, returns an empty pointer.
FixedPointType const* fixedPointType() const;
@ -582,7 +583,7 @@ class StringLiteralType: public Type
{
public:
explicit StringLiteralType(Literal const& _literal);
explicit StringLiteralType(std::string const& _value);
explicit StringLiteralType(std::string _value);
Category category() const override { return Category::StringLiteral; }
@ -705,6 +706,9 @@ public:
/// never change the contents of the original value.
bool isPointer() const;
/// @returns true if this is valid to be stored in data location _loc
virtual BoolResult validForLocation(DataLocation _loc) const = 0;
bool operator==(ReferenceType const& _other) const
{
return location() == _other.location() && isPointer() == _other.isPointer();
@ -744,11 +748,11 @@ public:
}
/// Constructor for a fixed-size array type ("type[20]")
ArrayType(DataLocation _location, Type const* _baseType, u256 const& _length):
ArrayType(DataLocation _location, Type const* _baseType, u256 _length):
ReferenceType(_location),
m_baseType(copyForLocationIfReference(_baseType)),
m_hasDynamicLength(false),
m_length(_length)
m_length(std::move(_length))
{}
Category category() const override { return Category::Array; }
@ -771,8 +775,7 @@ public:
TypePointer decodingType() const override;
TypeResult interfaceType(bool _inLibrary) const override;
/// @returns true if this is valid to be stored in calldata
bool validForCalldata() const;
BoolResult validForLocation(DataLocation _loc) const override;
/// @returns true if this is a byte array or a string
bool isByteArray() const { return m_arrayKind != ArrayKind::Ordinary; }
@ -826,8 +829,7 @@ public:
bool canLiveOutsideStorage() const override { return m_arrayType.canLiveOutsideStorage(); }
std::string toString(bool _short) const override;
/// @returns true if this is valid to be stored in calldata
bool validForCalldata() const { return m_arrayType.validForCalldata(); }
BoolResult validForLocation(DataLocation _loc) const override { return m_arrayType.validForLocation(_loc); }
ArrayType const& arrayType() const { return m_arrayType; }
u256 memoryDataSize() const override { solAssert(false, ""); }
@ -933,15 +935,9 @@ public:
Type const* encodingType() const override;
TypeResult interfaceType(bool _inLibrary) const override;
bool recursive() const
{
if (m_recursive.has_value())
return m_recursive.value();
BoolResult validForLocation(DataLocation _loc) const override;
interfaceType(false);
return m_recursive.value();
}
bool recursive() const;
std::unique_ptr<ReferenceType> copyForLocation(DataLocation _location, bool _isPointer) const override;
@ -970,7 +966,6 @@ private:
// Caches for interfaceType(bool)
mutable std::optional<TypeResult> m_interfaceType;
mutable std::optional<TypeResult> m_interfaceType_library;
mutable std::optional<bool> m_recursive;
};
/**
@ -1131,8 +1126,8 @@ public:
/// Detailed constructor, use with care.
FunctionType(
TypePointers const& _parameterTypes,
TypePointers const& _returnParameterTypes,
TypePointers _parameterTypes,
TypePointers _returnParameterTypes,
strings _parameterNames = strings(),
strings _returnParameterNames = strings(),
Kind _kind = Kind::Internal,
@ -1144,10 +1139,10 @@ public:
bool _saltSet = false,
bool _bound = false
):
m_parameterTypes(_parameterTypes),
m_returnParameterTypes(_returnParameterTypes),
m_parameterNames(_parameterNames),
m_returnParameterNames(_returnParameterNames),
m_parameterTypes(std::move(_parameterTypes)),
m_returnParameterTypes(std::move(_returnParameterTypes)),
m_parameterNames(std::move(_parameterNames)),
m_returnParameterNames(std::move(_returnParameterNames)),
m_kind(_kind),
m_stateMutability(_stateMutability),
m_arbitraryParameters(_arbitraryParameters),
@ -1395,7 +1390,6 @@ public:
std::string richIdentifier() const override;
bool operator==(Type const& _other) const override;
std::string toString(bool _short) const override;
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
private:

View File

@ -502,6 +502,7 @@ void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _
&meter,
_object,
_optimiserSettings.optimizeStackAllocation,
_optimiserSettings.yulOptimiserSteps,
_externalIdentifiers
);

View File

@ -733,7 +733,10 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
{
case Type::Category::Bool:
case Type::Category::Address:
solAssert(*type == *variable->annotation().type, "");
// Either both the literal and the variable are bools, or they are both addresses.
// If they are both bools, comparing category is the same as comparing the types.
// If they are both addresses, compare category so that payable/nonpayable is not compared.
solAssert(type->category() == variable->annotation().type->category(), "");
value = type->literalValue(literal);
break;
case Type::Category::StringLiteral:
@ -949,43 +952,7 @@ void ContractCompiler::handleCatch(vector<ASTPointer<TryCatchClause>> const& _ca
// Try to decode the error message.
// If this fails, leaves 0 on the stack, otherwise the pointer to the data string.
m_context << u256(0);
m_context.appendInlineAssembly(
util::Whiskers(R"({
data := mload(0x40)
mstore(data, 0)
for {} 1 {} {
if lt(returndatasize(), 0x44) { data := 0 break }
returndatacopy(0, 0, 4)
let sig := <getSig>
if iszero(eq(sig, 0x<ErrorSignature>)) { data := 0 break }
returndatacopy(data, 4, sub(returndatasize(), 4))
let offset := mload(data)
if or(
gt(offset, 0xffffffffffffffff),
gt(add(offset, 0x24), returndatasize())
) {
data := 0
break
}
let msg := add(data, offset)
let length := mload(msg)
if gt(length, 0xffffffffffffffff) { data := 0 break }
let end := add(add(msg, 0x20), length)
if gt(end, add(data, returndatasize())) { data := 0 break }
mstore(0x40, and(add(end, 0x1f), not(0x1f)))
data := msg
break
}
})")
("ErrorSignature", errorHash)
("getSig",
m_context.evmVersion().hasBitwiseShifting() ?
"shr(224, mload(0))" :
"div(mload(0), " + (u256(1) << 224).str() + ")"
).render(),
{"data"}
);
m_context.callYulFunction(m_context.utilFunctions().tryDecodeErrorMessageFunction(), 0, 1);
m_context << Instruction::DUP1;
AssemblyItem decodeSuccessTag = m_context.appendConditionalJump();
m_context << Instruction::POP;

View File

@ -22,12 +22,14 @@
#include <libsolidity/codegen/ExpressionCompiler.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libsolidity/codegen/ReturnInfo.h>
#include <libsolidity/codegen/CompilerContext.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/codegen/LValue.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libevmasm/GasMeter.h>
#include <libsolutil/Common.h>
#include <libsolutil/Keccak256.h>
@ -346,15 +348,15 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple)
if (component)
{
component->accept(*this);
if (_tuple.annotation().lValueRequested)
if (_tuple.annotation().willBeWrittenTo)
{
solAssert(!!m_currentLValue, "");
lvalues.push_back(move(m_currentLValue));
}
}
else if (_tuple.annotation().lValueRequested)
else if (_tuple.annotation().willBeWrittenTo)
lvalues.push_back(unique_ptr<LValue>());
if (_tuple.annotation().lValueRequested)
if (_tuple.annotation().willBeWrittenTo)
{
if (_tuple.components().size() == 1)
m_currentLValue = move(lvalues[0]);
@ -1580,6 +1582,15 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
utils().storeStringData(contract.name());
}
else if (member == "interfaceId")
{
TypePointer arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition();
uint64_t result{0};
for (auto const& function: contract.interfaceFunctionList(false))
result ^= fromBigEndian<uint64_t>(function.first.ref());
m_context << (u256{result} << (256 - 32));
}
else if ((set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}).count(member))
{
// no-op
@ -2185,30 +2196,11 @@ void ExpressionCompiler::appendExternalFunctionCall(
solAssert(!_functionType.isBareCall(), "");
}
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
unsigned retSize = 0;
bool dynamicReturnSize = false;
TypePointers returnTypes;
if (!returnSuccessConditionAndReturndata)
{
if (haveReturndatacopy)
returnTypes = _functionType.returnParameterTypes();
else
returnTypes = _functionType.returnParameterTypesWithoutDynamicTypes();
for (auto const& retType: returnTypes)
if (retType->isDynamicallyEncoded())
{
solAssert(haveReturndatacopy, "");
dynamicReturnSize = true;
retSize = 0;
break;
}
else if (retType->decodingType())
retSize += retType->decodingType()->calldataEncodedSize();
else
retSize += retType->calldataEncodedSize();
}
ReturnInfo const returnInfo{m_context.evmVersion(), _functionType};
bool const haveReturndatacopy = m_context.evmVersion().supportsReturndata();
unsigned const retSize = returnInfo.estimatedReturnSize;
bool const dynamicReturnSize = returnInfo.dynamicReturnSize;
TypePointers const& returnTypes = returnInfo.returnTypes;
// Evaluate arguments.
TypePointers argumentTypes;

View File

@ -148,7 +148,7 @@ void ExpressionCompiler::setLValue(Expression const& _expression, Arguments cons
{
solAssert(!m_currentLValue, "Current LValue not reset before trying to set new one.");
std::unique_ptr<LValueType> lvalue = std::make_unique<LValueType>(m_context, _arguments...);
if (_expression.annotation().lValueRequested)
if (_expression.annotation().willBeWrittenTo)
m_currentLValue = move(lvalue);
else
lvalue->retrieveValue(_expression.location(), true);

View File

@ -34,6 +34,7 @@ string MultiUseYulFunctionCollector::requestedFunctions()
{
string result;
for (auto const& f: m_requestedFunctions)
// std::map guarantees ascending order when iterating through its keys.
result += f.second;
m_requestedFunctions.clear();
return result;

View File

@ -41,10 +41,15 @@ public:
std::string createFunction(std::string const& _name, std::function<std::string()> const& _creator);
/// @returns concatenation of all generated functions.
/// Guarantees that the order of functions in the generated code is deterministic and
/// platform-independent.
/// Clears the internal list, i.e. calling it again will result in an
/// empty return value.
std::string requestedFunctions();
/// @returns true IFF a function with the specified name has already been collected.
bool contains(std::string const& _name) const { return m_requestedFunctions.count(_name) > 0; }
private:
/// Map from function name to code for a multi-use function.
std::map<std::string, std::string> m_requestedFunctions;

View File

@ -0,0 +1,55 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libsolidity/codegen/ReturnInfo.h>
#include <libsolidity/ast/Types.h>
#include <libsolidity/ast/AST.h>
using namespace solidity::frontend;
using namespace solidity::langutil;
ReturnInfo::ReturnInfo(EVMVersion const& _evmVersion, FunctionType const& _functionType)
{
FunctionType::Kind const funKind = _functionType.kind();
bool const haveReturndatacopy = _evmVersion.supportsReturndata();
bool const returnSuccessConditionAndReturndata =
funKind == FunctionType::Kind::BareCall ||
funKind == FunctionType::Kind::BareDelegateCall ||
funKind == FunctionType::Kind::BareStaticCall;
if (!returnSuccessConditionAndReturndata)
{
if (haveReturndatacopy)
returnTypes = _functionType.returnParameterTypes();
else
returnTypes = _functionType.returnParameterTypesWithoutDynamicTypes();
for (auto const& retType: returnTypes)
if (retType->isDynamicallyEncoded())
{
solAssert(haveReturndatacopy, "");
dynamicReturnSize = true;
estimatedReturnSize = 0;
break;
}
else if (retType->decodingType())
estimatedReturnSize += retType->decodingType()->calldataEncodedSize();
else
estimatedReturnSize += retType->calldataEncodedSize();
}
}

View File

@ -0,0 +1,47 @@
/*
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/>.
*/
/**
* Component that computes information relevant during decoding an external function
* call's return values.
*/
#pragma once
#include <liblangutil/EVMVersion.h>
#include <libsolidity/ast/Types.h>
namespace solidity::frontend
{
/**
* Computes and holds information relevant during decoding an external function
* call's return values.
*/
struct ReturnInfo
{
ReturnInfo(langutil::EVMVersion const& _evmVersion, FunctionType const& _functionType);
/// Vector of TypePointer, for each return variable. Dynamic types are already replaced if required.
TypePointers returnTypes = {};
/// Boolean, indicating whether or not return size is only known at runtime.
bool dynamicReturnSize = false;
/// Contains the at compile time estimated return size.
unsigned estimatedReturnSize = 0;
};
}

View File

@ -1337,6 +1337,34 @@ string YulUtilFunctions::allocationFunction()
});
}
string YulUtilFunctions::allocationTemporaryMemoryFunction()
{
string functionName = "allocateTemporaryMemory";
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>() -> memPtr {
memPtr := mload(<freeMemoryPointer>)
}
)")
("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
("functionName", functionName)
.render();
});
}
string YulUtilFunctions::releaseTemporaryMemoryFunction()
{
string functionName = "releaseTemporaryMemory";
return m_functionCollector.createFunction(functionName, [&](){
return Whiskers(R"(
function <functionName>() {
}
)")
("functionName", functionName)
.render();
});
}
string YulUtilFunctions::zeroMemoryArrayFunction(ArrayType const& _type)
{
if (_type.baseType()->hasSimpleZeroValueInMemory())
@ -2079,7 +2107,7 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const
{
conversions +=
suffixedVariableNameList("converted", destStackSize, destStackSize + toComponent->sizeOnStack()) +
" := " +
(toComponent->sizeOnStack() > 0 ? " := " : "") +
conversionFunction(*fromComponent, *toComponent) +
"(" +
suffixedVariableNameList("value", sourceStackSize, sourceStackSize + fromComponent->sizeOnStack()) +
@ -2089,12 +2117,13 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const
sourceStackSize += fromComponent->sizeOnStack();
}
return Whiskers(R"(
function <functionName>(<values>) -> <converted> {
function <functionName>(<values>) <arrow> <converted> {
<conversions>
}
)")
("functionName", functionName)
("values", suffixedVariableNameList("value", 0, sourceStackSize))
("arrow", destStackSize > 0 ? "->" : "")
("converted", suffixedVariableNameList("converted", 0, destStackSize))
("conversions", conversions)
.render();
@ -2252,3 +2281,120 @@ string YulUtilFunctions::revertReasonIfDebug(string const& _message)
{
return revertReasonIfDebug(m_revertStrings, _message);
}
string YulUtilFunctions::tryDecodeErrorMessageFunction()
{
string const functionName = "try_decode_error_message";
return m_functionCollector.createFunction(functionName, [&]() {
return util::Whiskers(R"(
function <functionName>() -> ret {
if lt(returndatasize(), 0x44) { leave }
returndatacopy(0, 0, 4)
let sig := <shr224>(mload(0))
if iszero(eq(sig, 0x<ErrorSignature>)) { leave }
let data := mload(<freeMemoryPointer>)
returndatacopy(data, 4, sub(returndatasize(), 4))
let offset := mload(data)
if or(
gt(offset, 0xffffffffffffffff),
gt(add(offset, 0x24), returndatasize())
) {
leave
}
let msg := add(data, offset)
let length := mload(msg)
if gt(length, 0xffffffffffffffff) { leave }
let end := add(add(msg, 0x20), length)
if gt(end, add(data, returndatasize())) { leave }
mstore(<freeMemoryPointer>, add(add(msg, 0x20), <roundUp>(length)))
ret := msg
}
)")
("functionName", functionName)
("shr224", shiftRightFunction(224))
("ErrorSignature", FixedHash<4>(util::keccak256("Error(string)")).hex())
("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
("roundUp", roundUpFunction())
.render();
});
}
string YulUtilFunctions::extractReturndataFunction()
{
string const functionName = "extract_returndata";
return m_functionCollector.createFunction(functionName, [&]() {
return util::Whiskers(R"(
function <functionName>() -> data {
<?supportsReturndata>
switch returndatasize()
case 0 {
data := <emptyArray>()
}
default {
// allocate some memory into data of size returndatasize() + PADDING
data := <allocate>(<roundUp>(add(returndatasize(), 0x20)))
// store array length into the front
mstore(data, returndatasize())
// append to data
returndatacopy(add(data, 0x20), 0, returndatasize())
}
<!supportsReturndata>
data := <emptyArray>()
</supportsReturndata>
}
)")
("functionName", functionName)
("supportsReturndata", m_evmVersion.supportsReturndata())
("allocate", allocationFunction())
("roundUp", roundUpFunction())
("emptyArray", zeroValueFunction(*TypeProvider::bytesMemory()))
.render();
});
}
string YulUtilFunctions::copyConstructorArgumentsToMemoryFunction(
ContractDefinition const& _contract,
string const& _creationObjectName
)
{
string functionName = "copy_arguments_for_constructor_" +
toString(_contract.constructor()->id()) +
"_object_" +
_contract.name() +
"_" +
toString(_contract.id());
return m_functionCollector.createFunction(functionName, [&]() {
string returnParams = suffixedVariableNameList("ret_param_",0, _contract.constructor()->parameters().size());
ABIFunctions abiFunctions(m_evmVersion, m_revertStrings, m_functionCollector);
return util::Whiskers(R"(
function <functionName>() -> <retParams> {
let programSize := datasize("<object>")
let argSize := sub(codesize(), programSize)
let memoryDataOffset := <allocate>(argSize)
codecopy(memoryDataOffset, programSize, argSize)
<retParams> := <abiDecode>(memoryDataOffset, add(memoryDataOffset, argSize))
}
)")
("functionName", functionName)
("retParams", returnParams)
("object", _creationObjectName)
("allocate", allocationFunction())
("abiDecode", abiFunctions.tupleDecoder(FunctionType(*_contract.constructor()).parameterTypes(), true))
.render();
});
}

View File

@ -22,6 +22,7 @@
#include <liblangutil/EVMVersion.h>
#include <libsolidity/ast/Types.h>
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
#include <libsolidity/interface/DebugSettings.h>
@ -252,6 +253,13 @@ public:
/// Return value: pointer
std::string allocationFunction();
/// @returns the name of the function that allocates temporary memory with predefined size
/// Return value: pointer
std::string allocationTemporaryMemoryFunction();
/// @returns the name of the function that releases previously allocated temporary memory
std::string releaseTemporaryMemoryFunction();
/// @returns the name of a function that zeroes an array.
/// signature: (dataStart, dataSizeInBytes) ->
std::string zeroMemoryArrayFunction(ArrayType const& _type);
@ -322,6 +330,27 @@ public:
static std::string revertReasonIfDebug(RevertStrings revertStrings, std::string const& _message = "");
std::string revertReasonIfDebug(std::string const& _message = "");
/// Returns the name of a function that decodes an error message.
/// signature: () -> arrayPtr
///
/// Returns a newly allocated `bytes memory` array containing the decoded error message
/// or 0 on failure.
std::string tryDecodeErrorMessageFunction();
/// Returns a function name that returns a newly allocated `bytes` array that contains the return data.
///
/// If returndatacopy() is not supported by the underlying target, a empty array will be returned instead.
std::string extractReturndataFunction();
/// @returns function name that returns constructor arguments copied to memory
/// signature: () -> arguments
std::string copyConstructorArgumentsToMemoryFunction(
ContractDefinition const& _contract,
std::string const& _creationObjectName
);
private:
/// Special case of conversionFunction - handles everything that does not
/// use exactly one variable to hold the value.

View File

@ -21,6 +21,7 @@
#include <libsolidity/codegen/ir/IRGenerationContext.h>
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/codegen/ABIFunctions.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h>
@ -32,6 +33,25 @@ using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
string IRGenerationContext::enqueueFunctionForCodeGeneration(FunctionDefinition const& _function)
{
string name = functionName(_function);
if (!m_functions.contains(name))
m_functionGenerationQueue.insert(&_function);
return name;
}
FunctionDefinition const* IRGenerationContext::dequeueFunctionForCodeGeneration()
{
solAssert(!m_functionGenerationQueue.empty(), "");
FunctionDefinition const* result = *m_functionGenerationQueue.begin();
m_functionGenerationQueue.erase(m_functionGenerationQueue.begin());
return result;
}
ContractDefinition const& IRGenerationContext::mostDerivedContract() const
{
solAssert(m_mostDerivedContract, "Most derived contract requested but not set.");
@ -77,9 +97,13 @@ string IRGenerationContext::functionName(VariableDeclaration const& _varDecl)
return "getter_fun_" + _varDecl.name() + "_" + to_string(_varDecl.id());
}
string IRGenerationContext::virtualFunctionName(FunctionDefinition const& _functionDeclaration)
string IRGenerationContext::creationObjectName(ContractDefinition const& _contract) const
{
return functionName(_functionDeclaration.resolveVirtual(mostDerivedContract()));
return _contract.name() + "_" + toString(_contract.id());
}
string IRGenerationContext::runtimeObjectName(ContractDefinition const& _contract) const
{
return _contract.name() + "_" + toString(_contract.id()) + "_deployed";
}
string IRGenerationContext::newYulVariable()
@ -87,6 +111,17 @@ string IRGenerationContext::newYulVariable()
return "_" + to_string(++m_varCounter);
}
string IRGenerationContext::trySuccessConditionVariable(Expression const& _expression) const
{
// NB: The TypeChecker already ensured that the Expression is of type FunctionCall.
solAssert(
static_cast<FunctionCallAnnotation const&>(_expression.annotation()).tryCall,
"Parameter must be a FunctionCall with tryCall-annotation set."
);
return "trySuccessCondition_" + to_string(_expression.id());
}
string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
{
string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
@ -97,7 +132,7 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
<#cases>
case <funID>
{
<out> := <name>(<in>)
<out> <assignment_op> <name>(<in>)
}
</cases>
default { invalid() }
@ -108,7 +143,13 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
YulUtilFunctions utils(m_evmVersion, m_revertStrings, m_functions);
templ("in", suffixedVariableNameList("in_", 0, _in));
templ("arrow", _out > 0 ? "->" : "");
templ("assignment_op", _out > 0 ? ":=" : "");
templ("out", suffixedVariableNameList("out_", 0, _out));
// UNIMPLEMENTED: Internal library calls via pointers are not implemented yet.
// We're not generating code for internal library functions here even though it's possible
// to call them via pointers. Right now such calls end up triggering the `default` case in
// the switch above.
vector<map<string, string>> functions;
for (auto const& contract: mostDerivedContract().annotation().linearizedBaseContracts)
for (FunctionDefinition const* function: contract->definedFunctions())
@ -126,6 +167,8 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
{ "funID", to_string(function->id()) },
{ "name", functionName(*function)}
});
enqueueFunctionForCodeGeneration(*function);
}
templ("cases", move(functions));
return templ.render();
@ -137,7 +180,13 @@ YulUtilFunctions IRGenerationContext::utils()
return YulUtilFunctions(m_evmVersion, m_revertStrings, m_functions);
}
ABIFunctions IRGenerationContext::abiFunctions()
{
return ABIFunctions(m_evmVersion, m_revertStrings, m_functions);
}
std::string IRGenerationContext::revertReasonIfDebug(std::string const& _message)
{
return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message);
}

View File

@ -20,6 +20,7 @@
#pragma once
#include <libsolidity/ast/AST.h>
#include <libsolidity/codegen/ir/IRVariable.h>
#include <libsolidity/interface/OptimiserSettings.h>
#include <libsolidity/interface/DebugSettings.h>
@ -30,6 +31,7 @@
#include <libsolutil/Common.h>
#include <set>
#include <string>
#include <memory>
#include <vector>
@ -37,11 +39,8 @@
namespace solidity::frontend
{
class ContractDefinition;
class VariableDeclaration;
class FunctionDefinition;
class Expression;
class YulUtilFunctions;
class ABIFunctions;
/**
* Class that contains contextual information during IR generation.
@ -61,6 +60,15 @@ public:
MultiUseYulFunctionCollector& functionCollector() { return m_functions; }
/// Adds a Solidity function to the function generation queue and returns the name of the
/// corresponding Yul function.
std::string enqueueFunctionForCodeGeneration(FunctionDefinition const& _function);
/// Pops one item from the function generation queue. Must not be called if the queue is empty.
FunctionDefinition const* dequeueFunctionForCodeGeneration();
bool functionGenerationQueueEmpty() { return m_functionGenerationQueue.empty(); }
/// Sets the most derived contract (the one currently being compiled)>
void setMostDerivedContract(ContractDefinition const& _mostDerivedContract)
{
@ -82,7 +90,9 @@ public:
std::string functionName(FunctionDefinition const& _function);
std::string functionName(VariableDeclaration const& _varDecl);
std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration);
std::string creationObjectName(ContractDefinition const& _contract) const;
std::string runtimeObjectName(ContractDefinition const& _contract) const;
std::string newYulVariable();
@ -93,12 +103,20 @@ public:
langutil::EVMVersion evmVersion() const { return m_evmVersion; };
ABIFunctions abiFunctions();
/// @returns code that stores @param _message for revert reason
/// if m_revertStrings is debug.
std::string revertReasonIfDebug(std::string const& _message = "");
RevertStrings revertStrings() const { return m_revertStrings; }
/// @returns the variable name that can be used to inspect the success or failure of an external
/// function call that was invoked as part of the try statement.
std::string trySuccessConditionVariable(Expression const& _expression) const;
std::set<ContractDefinition const*, ASTNode::CompareByID>& subObjectsCreated() { return m_subObjects; }
private:
langutil::EVMVersion m_evmVersion;
RevertStrings m_revertStrings;
@ -109,6 +127,17 @@ private:
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
MultiUseYulFunctionCollector m_functions;
size_t m_varCounter = 0;
/// Function definitions queued for code generation. They're the Solidity functions whose calls
/// were discovered by the IR generator during AST traversal.
/// Note that the queue gets filled in a lazy way - new definitions can be added while the
/// collected ones get removed and traversed.
/// The order and duplicates are irrelevant here (hence std::set rather than std::queue) as
/// long as the order of Yul functions in the generated code is deterministic and the same on
/// all platforms - which is a property guaranteed by MultiUseYulFunctionCollector.
std::set<FunctionDefinition const*> m_functionGenerationQueue;
std::set<ContractDefinition const*, ASTNode::CompareByID> m_subObjects;
};
}

View File

@ -48,9 +48,12 @@ using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
pair<string, string> IRGenerator::run(
ContractDefinition const& _contract,
map<ContractDefinition const*, string const> const& _otherYulSources
)
{
string const ir = yul::reindent(generate(_contract));
string const ir = yul::reindent(generate(_contract, _otherYulSources));
yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
if (!asmStack.parseAndAnalyze("", ir))
@ -73,15 +76,28 @@ pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
return {warning + ir, warning + asmStack.print()};
}
string IRGenerator::generate(ContractDefinition const& _contract)
string IRGenerator::generate(
ContractDefinition const& _contract,
map<ContractDefinition const*, string const> const& _otherYulSources
)
{
solUnimplementedAssert(!_contract.isLibrary(), "Libraries not yet implemented.");
auto subObjectSources = [&_otherYulSources](std::set<ContractDefinition const*, ASTNode::CompareByID> const& subObjects) -> string
{
std::string subObjectsSources;
for (ContractDefinition const* subObject: subObjects)
subObjectsSources += _otherYulSources.at(subObject);
return subObjectsSources;
};
Whiskers t(R"(
object "<CreationObject>" {
code {
<memoryInit>
<constructor>
<callValueCheck>
<?notLibrary>
<?constructorHasParams> let <constructorParams> := <copyConstructorArguments>() </constructorHasParams>
<implicitConstructor>(<constructorParams>)
</notLibrary>
<deploy>
<functions>
}
@ -91,32 +107,46 @@ string IRGenerator::generate(ContractDefinition const& _contract)
<dispatch>
<runtimeFunctions>
}
<runtimeSubObjects>
}
<subObjects>
}
)");
resetContext(_contract);
t("CreationObject", creationObjectName(_contract));
t("CreationObject", m_context.creationObjectName(_contract));
t("memoryInit", memoryInit());
t("constructor", constructorCode(_contract));
t("notLibrary", !_contract.isLibrary());
FunctionDefinition const* constructor = _contract.constructor();
t("callValueCheck", !constructor || !constructor->isPayable() ? callValueCheck() : "");
vector<string> constructorParams;
if (constructor && !constructor->parameters().empty())
{
for (size_t i = 0; i < constructor->parameters().size(); ++i)
constructorParams.emplace_back(m_context.newYulVariable());
t(
"copyConstructorArguments",
m_utils.copyConstructorArgumentsToMemoryFunction(_contract, m_context.creationObjectName(_contract))
);
}
t("constructorParams", joinHumanReadable(constructorParams));
t("constructorHasParams", !constructorParams.empty());
t("implicitConstructor", implicitConstructorName(_contract));
t("deploy", deployCode(_contract));
// We generate code for all functions and rely on the optimizer to remove them again
// TODO it would probably be better to only generate functions when internalDispatch or
// virtualFunctionName is called - same below.
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
for (auto const* fun: contract->definedFunctions())
generateFunction(*fun);
generateImplicitConstructors(_contract);
generateQueuedFunctions();
t("functions", m_context.functionCollector().requestedFunctions());
t("subObjects", subObjectSources(m_context.subObjectsCreated()));
resetContext(_contract);
m_context.setMostDerivedContract(_contract);
t("RuntimeObject", runtimeObjectName(_contract));
t("RuntimeObject", m_context.runtimeObjectName(_contract));
t("dispatch", dispatchRoutine(_contract));
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
for (auto const* fun: contract->definedFunctions())
generateFunction(*fun);
generateQueuedFunctions();
t("runtimeFunctions", m_context.functionCollector().requestedFunctions());
t("runtimeSubObjects", subObjectSources(m_context.subObjectsCreated()));
return t.render();
}
@ -127,6 +157,13 @@ string IRGenerator::generate(Block const& _block)
return generator.code();
}
void IRGenerator::generateQueuedFunctions()
{
while (!m_context.functionGenerationQueueEmpty())
// NOTE: generateFunction() may modify function generation queue
generateFunction(*m_context.dequeueFunctionForCodeGeneration());
}
string IRGenerator::generateFunction(FunctionDefinition const& _function)
{
string functionName = m_context.functionName(_function);
@ -239,64 +276,116 @@ string IRGenerator::generateInitialAssignment(VariableDeclaration const& _varDec
return generator.code();
}
string IRGenerator::constructorCode(ContractDefinition const& _contract)
pair<string, map<ContractDefinition const*, string>> IRGenerator::evaluateConstructorArguments(
ContractDefinition const& _contract
)
{
// Initialization of state variables in base-to-derived order.
solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library.");
map<ContractDefinition const*, string> constructorParams;
vector<pair<ContractDefinition const*, std::vector<ASTPointer<Expression>>const *>> baseConstructorArguments;
using boost::adaptors::reverse;
for (ASTPointer<InheritanceSpecifier> const& base: _contract.baseContracts())
if (FunctionDefinition const* baseConstructor = dynamic_cast<ContractDefinition const*>(
base->name().annotation().referencedDeclaration
)->constructor(); baseConstructor && base->arguments())
baseConstructorArguments.emplace_back(
dynamic_cast<ContractDefinition const*>(baseConstructor->scope()),
base->arguments()
);
ostringstream out;
if (FunctionDefinition const* constructor = _contract.constructor())
for (auto const& modifier: constructor->modifiers())
if (FunctionDefinition const* baseConstructor = dynamic_cast<ContractDefinition const*>(
modifier->name()->annotation().referencedDeclaration
)->constructor(); baseConstructor && modifier->arguments())
baseConstructorArguments.emplace_back(
dynamic_cast<ContractDefinition const*>(baseConstructor->scope()),
modifier->arguments()
);
FunctionDefinition const* constructor = _contract.constructor();
if (constructor && !constructor->isPayable())
out << callValueCheck();
for (ContractDefinition const* contract: reverse(_contract.annotation().linearizedBaseContracts))
IRGeneratorForStatements generator{m_context, m_utils};
for (auto&& [baseContract, arguments]: baseConstructorArguments)
{
out <<
"\n// Begin state variable initialization for contract \"" <<
contract->name() <<
"\" (" <<
contract->stateVariables().size() <<
" variables)\n";
IRGeneratorForStatements generator{m_context, m_utils};
for (VariableDeclaration const* variable: contract->stateVariables())
if (!variable->isConstant() && !variable->immutable())
generator.initializeStateVar(*variable);
out << generator.code();
out << "// End state variable initialization for contract \"" << contract->name() << "\".\n";
solAssert(baseContract && arguments, "");
if (baseContract->constructor() && !arguments->empty())
{
vector<string> params;
for (size_t i = 0; i < arguments->size(); ++i)
params.emplace_back(generator.evaluateExpression(
*(arguments->at(i)),
*(baseContract->constructor()->parameters()[i]->type())
).commaSeparatedList());
constructorParams[baseContract] = joinHumanReadable(params);
}
}
if (constructor)
return {generator.code(), constructorParams};
}
string IRGenerator::initStateVariables(ContractDefinition const& _contract)
{
IRGeneratorForStatements generator{m_context, m_utils};
for (VariableDeclaration const* variable: _contract.stateVariables())
if (!variable->isConstant() && !variable->immutable())
generator.initializeStateVar(*variable);
return generator.code();
}
void IRGenerator::generateImplicitConstructors(ContractDefinition const& _contract)
{
auto listAllParams = [&](
map<ContractDefinition const*, string> const& baseParams) -> string
{
ABIFunctions abiFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
unsigned paramVars = make_shared<TupleType>(constructor->functionType(false)->parameterTypes())->sizeOnStack();
vector<string> params;
for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts)
if (baseParams.count(contract))
params.emplace_back(baseParams.at(contract));
return joinHumanReadable(params);
};
Whiskers t(R"X(
let programSize := datasize("<object>")
let argSize := sub(codesize(), programSize)
map<ContractDefinition const*, string> baseConstructorParams;
for (size_t i = 0; i < _contract.annotation().linearizedBaseContracts.size(); ++i)
{
ContractDefinition const* contract = _contract.annotation().linearizedBaseContracts[i];
baseConstructorParams.erase(contract);
let memoryDataOffset := <allocate>(argSize)
codecopy(memoryDataOffset, programSize, argSize)
m_context.functionCollector().createFunction(implicitConstructorName(*contract), [&]() {
Whiskers t(R"(
function <functionName>(<params><comma><baseParams>) {
<evalBaseArguments>
<?hasNextConstructor> <nextConstructor>(<nextParams>) </hasNextConstructor>
<initStateVariables>
<userDefinedConstructorBody>
}
)");
string params;
if (contract->constructor())
for (ASTPointer<VariableDeclaration> const& varDecl: contract->constructor()->parameters())
params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
t("params", params);
string baseParamsString = listAllParams(baseConstructorParams);
t("baseParams", baseParamsString);
t("comma", !params.empty() && !baseParamsString.empty() ? ", " : "");
t("functionName", implicitConstructorName(*contract));
pair<string, map<ContractDefinition const*, string>> evaluatedArgs = evaluateConstructorArguments(*contract);
baseConstructorParams.insert(evaluatedArgs.second.begin(), evaluatedArgs.second.end());
t("evalBaseArguments", evaluatedArgs.first);
if (i < _contract.annotation().linearizedBaseContracts.size() - 1)
{
t("hasNextConstructor", true);
ContractDefinition const* nextContract = _contract.annotation().linearizedBaseContracts[i + 1];
t("nextConstructor", implicitConstructorName(*nextContract));
t("nextParams", listAllParams(baseConstructorParams));
}
else
t("hasNextConstructor", false);
t("initStateVariables", initStateVariables(*contract));
t("userDefinedConstructorBody", contract->constructor() ? generate(contract->constructor()->body()) : "");
<assignToParams> <abiDecode>(memoryDataOffset, add(memoryDataOffset, argSize))
<constructorName>(<params>)
)X");
t("object", creationObjectName(_contract));
t("allocate", m_utils.allocationFunction());
t("assignToParams", paramVars == 0 ? "" : "let " + suffixedVariableNameList("param_", 0, paramVars) + " := ");
t("params", suffixedVariableNameList("param_", 0, paramVars));
t("abiDecode", abiFunctions.tupleDecoder(constructor->functionType(false)->parameterTypes(), true));
t("constructorName", m_context.functionName(*constructor));
out << t.render();
return t.render();
});
}
return out.str();
}
string IRGenerator::deployCode(ContractDefinition const& _contract)
@ -305,7 +394,7 @@ string IRGenerator::deployCode(ContractDefinition const& _contract)
codecopy(0, dataoffset("<object>"), datasize("<object>"))
return(0, datasize("<object>"))
)X");
t("object", runtimeObjectName(_contract));
t("object", m_context.runtimeObjectName(_contract));
return t.render();
}
@ -314,14 +403,9 @@ string IRGenerator::callValueCheck()
return "if callvalue() { revert(0, 0) }";
}
string IRGenerator::creationObjectName(ContractDefinition const& _contract)
string IRGenerator::implicitConstructorName(ContractDefinition const& _contract)
{
return _contract.name() + "_" + to_string(_contract.id());
}
string IRGenerator::runtimeObjectName(ContractDefinition const& _contract)
{
return _contract.name() + "_" + to_string(_contract.id()) + "_deployed";
return "constructor_" + _contract.name() + "_" + to_string(_contract.id());
}
string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
@ -370,7 +454,7 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
templ["retParams"] = suffixedVariableNameList("ret_", retVars, 0);
if (FunctionDefinition const* funDef = dynamic_cast<FunctionDefinition const*>(&type->declaration()))
templ["function"] = generateFunction(*funDef);
templ["function"] = m_context.enqueueFunctionForCodeGeneration(*funDef);
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(&type->declaration()))
templ["function"] = generateGetter(*varDecl);
else
@ -386,14 +470,14 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
string fallbackCode;
if (!fallback->isPayable())
fallbackCode += callValueCheck();
fallbackCode += generateFunction(*fallback) + "() stop()";
fallbackCode += m_context.enqueueFunctionForCodeGeneration(*fallback) + "() stop()";
t("fallback", fallbackCode);
}
else
t("fallback", "revert(0, 0)");
if (FunctionDefinition const* etherReceiver = _contract.receiveFunction())
t("receiveEther", generateFunction(*etherReceiver) + "() stop()");
t("receiveEther", m_context.enqueueFunctionForCodeGeneration(*etherReceiver) + "() stop()");
else
t("receiveEther", "");
return t.render();
@ -413,6 +497,10 @@ string IRGenerator::memoryInit()
void IRGenerator::resetContext(ContractDefinition const& _contract)
{
solAssert(
m_context.functionGenerationQueueEmpty(),
"Reset function generation queue while it still had functions."
);
solAssert(
m_context.functionCollector().requestedFunctions().empty(),
"Reset context while it still had functions."

View File

@ -50,12 +50,21 @@ public:
/// Generates and returns the IR code, in unoptimized and optimized form
/// (or just pretty-printed, depending on the optimizer settings).
std::pair<std::string, std::string> run(ContractDefinition const& _contract);
std::pair<std::string, std::string> run(
ContractDefinition const& _contract,
std::map<ContractDefinition const*, std::string const> const& _otherYulSources
);
private:
std::string generate(ContractDefinition const& _contract);
std::string generate(
ContractDefinition const& _contract,
std::map<ContractDefinition const*, std::string const> const& _otherYulSources
);
std::string generate(Block const& _block);
/// Generates code for all the functions from the function generation queue.
/// The resulting code is stored in the function collector in IRGenerationContext.
void generateQueuedFunctions();
/// Generates code for and returns the name of the function.
std::string generateFunction(FunctionDefinition const& _function);
/// Generates a getter for the given declaration and returns its name
@ -64,12 +73,27 @@ private:
/// Generates code that assigns the initial value of the respective type.
std::string generateInitialAssignment(VariableDeclaration const& _varDecl);
std::string constructorCode(ContractDefinition const& _contract);
/// Generates implicit constructors for all contracts in the inheritance hierarchy of
/// @a _contract
/// If there are user defined constructors, their body will be included in implicit constructors body.
void generateImplicitConstructors(ContractDefinition const& _contract);
/// Evaluates constructor's arguments for all base contracts (listed in inheritance specifiers) of
/// @a _contract
/// @returns Pair of expressions needed to evaluate params and list of parameters in a map contract -> params
std::pair<std::string, std::map<ContractDefinition const*, std::string>> evaluateConstructorArguments(
ContractDefinition const& _contract
);
/// Initializes state variables of
/// @a _contract
/// @returns Source code to initialize state variables
std::string initStateVariables(ContractDefinition const& _contract);
std::string deployCode(ContractDefinition const& _contract);
std::string callValueCheck();
std::string creationObjectName(ContractDefinition const& _contract);
std::string runtimeObjectName(ContractDefinition const& _contract);
std::string implicitConstructorName(ContractDefinition const& _contract);
std::string dispatchRoutine(ContractDefinition const& _contract);

View File

@ -27,6 +27,7 @@
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/codegen/ABIFunctions.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/codegen/ReturnInfo.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libevmasm/GasMeter.h>
@ -41,6 +42,7 @@
#include <libsolutil/Keccak256.h>
#include <libsolutil/Visitor.h>
#include <boost/range/adaptor/reversed.hpp>
#include <boost/range/adaptor/transformed.hpp>
using namespace std;
@ -167,6 +169,14 @@ void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _va
assign(m_context.localVariable(_varDecl), zero);
}
IRVariable IRGeneratorForStatements::evaluateExpression(Expression const& _expression, Type const& _targetType)
{
_expression.accept(*this);
IRVariable variable{m_context.newYulVariable(), _targetType};
define(variable, _expression);
return variable;
}
void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement)
{
if (Expression const* expression = _varDeclStatement.initialValue())
@ -255,14 +265,14 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
solUnimplementedAssert(false, "");
else
{
bool lValueRequested = _tuple.annotation().lValueRequested;
if (lValueRequested)
bool willBeWrittenTo = _tuple.annotation().willBeWrittenTo;
if (willBeWrittenTo)
solAssert(!m_currentLValue, "");
if (_tuple.components().size() == 1)
{
solAssert(_tuple.components().front(), "");
_tuple.components().front()->accept(*this);
if (lValueRequested)
if (willBeWrittenTo)
solAssert(!!m_currentLValue, "");
else
define(_tuple, *_tuple.components().front());
@ -274,7 +284,7 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
if (auto const& component = _tuple.components()[i])
{
component->accept(*this);
if (lValueRequested)
if (willBeWrittenTo)
{
solAssert(!!m_currentLValue, "");
lvalues.emplace_back(std::move(m_currentLValue));
@ -283,10 +293,10 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
else
define(IRVariable(_tuple).tupleComponent(i), *component);
}
else if (lValueRequested)
else if (willBeWrittenTo)
lvalues.emplace_back();
if (_tuple.annotation().lValueRequested)
if (_tuple.annotation().willBeWrittenTo)
m_currentLValue.emplace(IRLValue{
*_tuple.annotation().type,
IRLValue::Tuple{std::move(lvalues)}
@ -557,9 +567,20 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
arguments.push_back(callArguments[std::distance(callArgumentNames.begin(), it)]);
}
if (auto memberAccess = dynamic_cast<MemberAccess const*>(&_functionCall.expression()))
if (auto expressionType = dynamic_cast<TypeType const*>(memberAccess->expression().annotation().type))
if (auto contractType = dynamic_cast<ContractType const*>(expressionType->actualType()))
solUnimplementedAssert(
!contractType->contractDefinition().isLibrary() || functionType->kind() == FunctionType::Kind::Internal,
"Only internal function calls implemented for libraries"
);
solUnimplementedAssert(!functionType->bound(), "");
switch (functionType->kind())
{
case FunctionType::Kind::Declaration:
solAssert(false, "Attempted to generate code for calling a function definition.");
break;
case FunctionType::Kind::Internal:
{
vector<string> args;
@ -569,29 +590,60 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
else
args.emplace_back(convert(*arguments[i], *parameterTypes[i]).commaSeparatedList());
if (auto identifier = dynamic_cast<Identifier const*>(&_functionCall.expression()))
optional<FunctionDefinition const*> functionDef;
if (auto memberAccess = dynamic_cast<MemberAccess const*>(&_functionCall.expression()))
{
solAssert(!functionType->bound(), "");
if (auto functionDef = dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration))
solUnimplementedAssert(!functionType->bound(), "Internal calls to bound functions are not yet implemented for libraries and not allowed for contracts");
functionDef = dynamic_cast<FunctionDefinition const*>(memberAccess->annotation().referencedDeclaration);
if (functionDef.value() != nullptr)
solAssert(functionType->declaration() == *memberAccess->annotation().referencedDeclaration, "");
else
{
define(_functionCall) <<
m_context.virtualFunctionName(*functionDef) <<
"(" <<
joinHumanReadable(args) <<
")\n";
return;
solAssert(dynamic_cast<VariableDeclaration const*>(memberAccess->annotation().referencedDeclaration), "");
solAssert(!functionType->hasDeclaration(), "");
}
}
else if (auto identifier = dynamic_cast<Identifier const*>(&_functionCall.expression()))
{
solAssert(!functionType->bound(), "");
define(_functionCall) <<
m_context.internalDispatch(
TupleType(functionType->parameterTypes()).sizeOnStack(),
TupleType(functionType->returnParameterTypes()).sizeOnStack()
) <<
"(" <<
IRVariable(_functionCall.expression()).part("functionIdentifier").name() <<
joinHumanReadablePrefixed(args) <<
")\n";
if (auto unresolvedFunctionDef = dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration))
{
functionDef = &unresolvedFunctionDef->resolveVirtual(m_context.mostDerivedContract());
solAssert(functionType->declaration() == *identifier->annotation().referencedDeclaration, "");
}
else
{
functionDef = nullptr;
solAssert(dynamic_cast<VariableDeclaration const*>(identifier->annotation().referencedDeclaration), "");
solAssert(!functionType->hasDeclaration(), "");
}
}
else
// Not a simple expression like x or A.x
functionDef = nullptr;
solAssert(functionDef.has_value(), "");
solAssert(functionDef.value() == nullptr || functionDef.value()->isImplemented(), "");
if (functionDef.value() != nullptr)
define(_functionCall) <<
m_context.enqueueFunctionForCodeGeneration(*functionDef.value()) <<
"(" <<
joinHumanReadable(args) <<
")\n";
else
define(_functionCall) <<
// NOTE: internalDispatch() takes care of adding the function to function generation queue
m_context.internalDispatch(
TupleType(functionType->parameterTypes()).sizeOnStack(),
TupleType(functionType->returnParameterTypes()).sizeOnStack()
) <<
"(" <<
IRVariable(_functionCall.expression()).part("functionIdentifier").name() <<
joinHumanReadablePrefixed(args) <<
")\n";
break;
}
case FunctionType::Kind::External:
@ -678,6 +730,16 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
break;
}
case FunctionType::Kind::Revert:
{
solAssert(arguments.size() == parameterTypes.size(), "");
if (arguments.empty())
m_code << "revert(0, 0)\n";
else
solUnimplementedAssert(false, "");
break;
}
// Array creation using new
case FunctionType::Kind::ObjectCreation:
{
@ -711,6 +773,14 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
"))\n";
break;
}
case FunctionType::Kind::ECRecover:
case FunctionType::Kind::SHA256:
case FunctionType::Kind::RIPEMD160:
{
solAssert(!_functionCall.annotation().tryCall, "");
appendExternalFunctionCall(_functionCall, arguments);
break;
}
case FunctionType::Kind::ArrayPop:
{
auto const& memberAccessExpression = dynamic_cast<MemberAccess const&>(_functionCall.expression()).expression();
@ -754,6 +824,128 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
}
break;
}
case FunctionType::Kind::MetaType:
{
break;
}
case FunctionType::Kind::GasLeft:
{
define(_functionCall) << "gas()\n";
break;
}
case FunctionType::Kind::Selfdestruct:
{
solAssert(arguments.size() == 1, "");
define(_functionCall) << "selfdestruct(" << expressionAsType(*arguments.front(), *parameterTypes.front()) << ")\n";
break;
}
case FunctionType::Kind::Log0:
case FunctionType::Kind::Log1:
case FunctionType::Kind::Log2:
case FunctionType::Kind::Log3:
case FunctionType::Kind::Log4:
{
unsigned logNumber = int(functionType->kind()) - int(FunctionType::Kind::Log0);
solAssert(arguments.size() == logNumber + 1, "");
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
string indexedArgs;
for (unsigned arg = 0; arg < logNumber; ++arg)
indexedArgs += ", " + expressionAsType(*arguments[arg + 1], *(parameterTypes[arg + 1]));
Whiskers templ(R"({
let <pos> := <freeMemory>
let <end> := <encode>(<pos>, <nonIndexedArgs>)
<log>(<pos>, sub(<end>, <pos>) <indexedArgs>)
})");
templ("pos", m_context.newYulVariable());
templ("end", m_context.newYulVariable());
templ("freeMemory", freeMemory());
templ("encode", abi.tupleEncoder({arguments.front()->annotation().type}, {parameterTypes.front()}));
templ("nonIndexedArgs", IRVariable(*arguments.front()).commaSeparatedList());
templ("log", "log" + to_string(logNumber));
templ("indexedArgs", indexedArgs);
m_code << templ.render();
break;
}
case FunctionType::Kind::Creation:
{
solAssert(!functionType->gasSet(), "Gas limit set for contract creation.");
solAssert(
functionType->returnParameterTypes().size() == 1,
"Constructor should return only one type"
);
TypePointers argumentTypes;
string constructorParams;
for (ASTPointer<Expression const> const& arg: arguments)
{
argumentTypes.push_back(arg->annotation().type);
constructorParams += ", " + IRVariable{*arg}.commaSeparatedList();
}
ContractDefinition const* contract =
&dynamic_cast<ContractType const&>(*functionType->returnParameterTypes().front()).contractDefinition();
m_context.subObjectsCreated().insert(contract);
Whiskers t(R"(
let <memPos> := <allocateTemporaryMemory>()
let <memEnd> := add(<memPos>, datasize("<object>"))
if or(gt(<memEnd>, 0xffffffffffffffff), lt(<memEnd>, <memPos>)) { revert(0, 0) }
datacopy(<memPos>, dataoffset("<object>"), datasize("<object>"))
<memEnd> := <abiEncode>(<memEnd><constructorParams>)
<?saltSet>
let <retVars> := create2(<value>, <memPos>, sub(<memEnd>, <memPos>), <salt>)
<!saltSet>
let <retVars> := create(<value>, <memPos>, sub(<memEnd>, <memPos>))
</saltSet>
<releaseTemporaryMemory>()
)");
t("memPos", m_context.newYulVariable());
t("memEnd", m_context.newYulVariable());
t("allocateTemporaryMemory", m_utils.allocationTemporaryMemoryFunction());
t("releaseTemporaryMemory", m_utils.releaseTemporaryMemoryFunction());
t("object", m_context.creationObjectName(*contract));
t("abiEncode",
m_context.abiFunctions().tupleEncoder(argumentTypes, functionType->parameterTypes(),false)
);
t("constructorParams", constructorParams);
t("value", functionType->valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0");
t("saltSet", functionType->saltSet());
if (functionType->saltSet())
t("salt", IRVariable(_functionCall.expression()).part("salt").name());
t("retVars", IRVariable(_functionCall).commaSeparatedList());
m_code << t.render();
break;
}
case FunctionType::Kind::Send:
case FunctionType::Kind::Transfer:
{
solAssert(arguments.size() == 1 && parameterTypes.size() == 1, "");
string address{IRVariable(_functionCall.expression()).part("address").name()};
string value{expressionAsType(*arguments[0], *(parameterTypes[0]))};
Whiskers templ(R"(
let <gas> := 0
if iszero(<value>) { <gas> := <callStipend> }
let <success> := call(<gas>, <address>, <value>, 0, 0, 0, 0)
<?isTransfer>
if iszero(<success>) { <forwardingRevert>() }
</isTransfer>
)");
templ("gas", m_context.newYulVariable());
templ("callStipend", toString(evmasm::GasCosts::callStipend));
templ("address", address);
templ("value", value);
if (functionType->kind() == FunctionType::Kind::Transfer)
templ("success", m_context.newYulVariable());
else
templ("success", IRVariable(_functionCall).commaSeparatedList());
templ("isTransfer", functionType->kind() == FunctionType::Kind::Transfer);
templ("forwardingRevert", m_utils.forwardingRevertFunction());
m_code << templ.render();
break;
}
default:
solUnimplemented("FunctionKind " + toString(static_cast<int>(functionType->kind())) + " not yet implemented");
}
@ -840,11 +1032,18 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
case Type::Category::Function:
if (member == "selector")
{
solUnimplementedAssert(
dynamic_cast<FunctionType const&>(*_memberAccess.expression().annotation().type).kind() ==
FunctionType::Kind::External, ""
FunctionType const& functionType = dynamic_cast<FunctionType const&>(
*_memberAccess.expression().annotation().type
);
define(IRVariable{_memberAccess}, IRVariable(_memberAccess.expression()).part("functionIdentifier"));
if (functionType.kind() == FunctionType::Kind::External)
define(IRVariable{_memberAccess}, IRVariable(_memberAccess.expression()).part("functionIdentifier"));
else if (functionType.kind() == FunctionType::Kind::Declaration)
{
solAssert(functionType.hasDeclaration(), "");
define(IRVariable{_memberAccess}) << formatNumber(functionType.externalIdentifier() << 224) << "\n";
}
else
solAssert(false, "Invalid use of .selector");
}
else if (member == "address")
{
@ -904,6 +1103,15 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
{
solUnimplementedAssert(false, "");
}
else if (member == "interfaceId")
{
TypePointer arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition();
uint64_t result{0};
for (auto const& function: contract.interfaceFunctionList(false))
result ^= fromBigEndian<uint64_t>(function.first.ref());
define(_memberAccess) << formatNumber(u256{result} << (256 - 32)) << "\n";
}
else if (set<string>{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}.count(member))
{
// no-op
@ -971,6 +1179,78 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
solAssert(false, "Illegal fixed bytes member.");
break;
}
case Type::Category::TypeType:
{
Type const& actualType = *dynamic_cast<TypeType const&>(
*_memberAccess.expression().annotation().type
).actualType();
if (actualType.category() == Type::Category::Contract)
{
if (auto const* variable = dynamic_cast<VariableDeclaration const*>(_memberAccess.annotation().referencedDeclaration))
handleVariableReference(*variable, _memberAccess);
else if (auto const* funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type))
{
switch (funType->kind())
{
case FunctionType::Kind::Declaration:
break;
case FunctionType::Kind::Internal:
if (auto const* function = dynamic_cast<FunctionDefinition const*>(_memberAccess.annotation().referencedDeclaration))
define(_memberAccess) << to_string(function->id()) << "\n";
else
solAssert(false, "Function not found in member access");
break;
case FunctionType::Kind::Event:
solAssert(
dynamic_cast<EventDefinition const*>(_memberAccess.annotation().referencedDeclaration),
"Event not found"
);
// the call will do the resolving
break;
case FunctionType::Kind::DelegateCall:
define(IRVariable(_memberAccess).part("address"), _memberAccess.expression());
define(IRVariable(_memberAccess).part("functionIdentifier")) << formatNumber(funType->externalIdentifier()) << "\n";
break;
case FunctionType::Kind::External:
case FunctionType::Kind::Creation:
case FunctionType::Kind::Send:
case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareCallCode:
case FunctionType::Kind::BareDelegateCall:
case FunctionType::Kind::BareStaticCall:
case FunctionType::Kind::Transfer:
case FunctionType::Kind::Log0:
case FunctionType::Kind::Log1:
case FunctionType::Kind::Log2:
case FunctionType::Kind::Log3:
case FunctionType::Kind::Log4:
case FunctionType::Kind::ECRecover:
case FunctionType::Kind::SHA256:
case FunctionType::Kind::RIPEMD160:
default:
solAssert(false, "unsupported member function");
}
}
else if (dynamic_cast<TypeType const*>(_memberAccess.annotation().type))
{
// no-op
}
else
// The old code generator had a generic "else" case here
// without any specific code being generated,
// but it would still be better to have an exhaustive list.
solAssert(false, "");
}
else if (EnumType const* enumType = dynamic_cast<EnumType const*>(&actualType))
define(_memberAccess) << to_string(enumType->memberValue(_memberAccess.memberName())) << "\n";
else
// The old code generator had a generic "else" case here
// without any specific code being generated,
// but it would still be better to have an exhaustive list.
solAssert(false, "");
break;
}
default:
solAssert(false, "Member access to unknown type.");
}
@ -985,7 +1265,7 @@ bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm)
solAssert(holds_alternative<yul::Block>(modified), "");
// Do not provide dialect so that we get the full type information.
m_code << yul::AsmPrinter()(std::get<yul::Block>(std::move(modified))) << "\n";
m_code << yul::AsmPrinter()(std::get<yul::Block>(modified)) << "\n";
return false;
}
@ -1186,31 +1466,10 @@ void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
define(_identifier) << to_string(functionDef->resolveVirtual(m_context.mostDerivedContract()).id()) << "\n";
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
handleVariableReference(*varDecl, _identifier);
else if (dynamic_cast<ContractDefinition const*>(declaration))
{
// TODO for the constant case, we have to be careful:
// If the value is visited twice, `defineExpression` is called twice on
// the same expression.
solUnimplementedAssert(!varDecl->isConstant(), "");
solUnimplementedAssert(!varDecl->immutable(), "");
if (m_context.isLocalVariable(*varDecl))
setLValue(_identifier, IRLValue{
*varDecl->annotation().type,
IRLValue::Stack{m_context.localVariable(*varDecl)}
});
else if (m_context.isStateVariable(*varDecl))
setLValue(_identifier, IRLValue{
*varDecl->annotation().type,
IRLValue::Storage{
toCompactHexWithPrefix(m_context.storageLocationOfVariable(*varDecl).first),
m_context.storageLocationOfVariable(*varDecl).second
}
});
else
solAssert(false, "Invalid variable kind.");
}
else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
{
solUnimplementedAssert(!contract->isLibrary(), "Libraries not yet supported.");
// no-op
}
else if (dynamic_cast<EventDefinition const*>(declaration))
{
@ -1249,6 +1508,33 @@ bool IRGeneratorForStatements::visit(Literal const& _literal)
return false;
}
void IRGeneratorForStatements::handleVariableReference(
VariableDeclaration const& _variable,
Expression const& _referencingExpression
)
{
// TODO for the constant case, we have to be careful:
// If the value is visited twice, `defineExpression` is called twice on
// the same expression.
solUnimplementedAssert(!_variable.isConstant(), "");
solUnimplementedAssert(!_variable.immutable(), "");
if (m_context.isLocalVariable(_variable))
setLValue(_referencingExpression, IRLValue{
*_variable.annotation().type,
IRLValue::Stack{m_context.localVariable(_variable)}
});
else if (m_context.isStateVariable(_variable))
setLValue(_referencingExpression, IRLValue{
*_variable.annotation().type,
IRLValue::Storage{
toCompactHexWithPrefix(m_context.storageLocationOfVariable(_variable).first),
m_context.storageLocationOfVariable(_variable).second
}
});
else
solAssert(false, "Invalid variable kind.");
}
void IRGeneratorForStatements::appendExternalFunctionCall(
FunctionCall const& _functionCall,
vector<ASTPointer<Expression const>> const& _arguments
@ -1260,46 +1546,23 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
_arguments.size() == funType.parameterTypes().size(), ""
);
solUnimplementedAssert(!funType.bound(), "");
FunctionType::Kind funKind = funType.kind();
FunctionType::Kind const funKind = funType.kind();
solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), "");
solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed.");
bool returnSuccessConditionAndReturndata = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall;
bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
bool const isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
bool const useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
unsigned estimatedReturnSize = 0;
bool dynamicReturnSize = false;
TypePointers returnTypes;
if (!returnSuccessConditionAndReturndata)
{
if (haveReturndatacopy)
returnTypes = funType.returnParameterTypes();
else
returnTypes = funType.returnParameterTypesWithoutDynamicTypes();
for (auto const& retType: returnTypes)
if (retType->isDynamicallyEncoded())
{
solAssert(haveReturndatacopy, "");
dynamicReturnSize = true;
estimatedReturnSize = 0;
break;
}
else if (retType->decodingType())
estimatedReturnSize += retType->decodingType()->calldataEncodedSize();
else
estimatedReturnSize += retType->calldataEncodedSize();
}
ReturnInfo const returnInfo{m_context.evmVersion(), funType};
TypePointers argumentTypes;
vector<string> argumentStrings;
for (auto const& arg: _arguments)
{
argumentTypes.emplace_back(&type(*arg));
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
if (IRVariable(*arg).type().sizeOnStack() > 0)
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
}
string argumentString = argumentStrings.empty() ? ""s : (", " + joinHumanReadable(argumentStrings));
@ -1311,41 +1574,92 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
// (which we would have to subtract from the gas left)
// We could also just use MLOAD; POP right before the gas calculation, but the optimizer
// would remove that, so we use MSTORE here.
if (!funType.gasSet() && estimatedReturnSize > 0)
m_code << "mstore(add(" << freeMemory() << ", " << to_string(estimatedReturnSize) << "), 0)\n";
if (!funType.gasSet() && returnInfo.estimatedReturnSize > 0)
m_code << "mstore(add(" << freeMemory() << ", " << to_string(returnInfo.estimatedReturnSize) << "), 0)\n";
}
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
solUnimplementedAssert(!funType.isBareCall(), "");
Whiskers templ(R"(
<?checkExistence>
if iszero(extcodesize(<address>)) { revert(0, 0) }
</checkExistence>
// storage for arguments and returned data
let <pos> := <freeMemory>
<?bareCall>
<!bareCall>
mstore(<pos>, <shl28>(<funId>))
</bareCall>
let <end> := <encodeArgs>(
<?bareCall>
<pos>
<!bareCall>
add(<pos>, 4)
</bareCall>
<argumentString>
)
mstore(<pos>, <shl28>(<funId>))
let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
let <success> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
<?noTryCall>
if iszero(<success>) { <forwardingRevert>() }
</noTryCall>
<?hasRetVars> let <retVars> </hasRetVars>
if <success> {
<?dynamicReturnSize>
// copy dynamic return data out
returndatacopy(<pos>, 0, returndatasize())
</dynamicReturnSize>
let <result> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
if iszero(<result>) { <forwardingRevert>() }
// update freeMemoryPointer according to dynamic return size
mstore(<freeMemoryPointer>, add(<pos>, <roundUp>(<returnSize>)))
<?dynamicReturnSize>
returndatacopy(<pos>, 0, returndatasize())
</dynamicReturnSize>
mstore(<freeMemoryPointer>, add(<pos>, and(add(<returnSize>, 0x1f), not(0x1f))))
<?returns> let <retVars> := </returns> <abiDecode>(<pos>, add(<pos>, <returnSize>))
// decode return parameters from external try-call into retVars
<?hasRetVars> <retVars> := </hasRetVars> <abiDecode>(<pos>, add(<pos>, <returnSize>))
}
)");
templ("pos", m_context.newYulVariable());
templ("end", m_context.newYulVariable());
templ("result", m_context.newYulVariable());
templ("bareCall", funType.isBareCall());
if (_functionCall.annotation().tryCall)
templ("success", m_context.trySuccessConditionVariable(_functionCall));
else
templ("success", m_context.newYulVariable());
templ("freeMemory", freeMemory());
templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4)));
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
templ("address", IRVariable(_functionCall.expression()).part("address").name());
if (!funType.isBareCall())
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
if (funKind == FunctionType::Kind::ECRecover)
templ("address", "1");
else if (funKind == FunctionType::Kind::SHA256)
templ("address", "2");
else if (funKind == FunctionType::Kind::RIPEMD160)
templ("address", "3");
else
templ("address", IRVariable(_functionCall.expression()).part("address").name());
// Always use the actual return length, and not our calculated expected length, if returndatacopy is supported.
// This ensures it can catch badly formatted input from external calls.
if (m_context.evmVersion().supportsReturndata())
templ("returnSize", "returndatasize()");
else
templ("returnSize", to_string(returnInfo.estimatedReturnSize));
templ("reservedReturnSize", returnInfo.dynamicReturnSize ? "0" : to_string(returnInfo.estimatedReturnSize));
string const retVars = IRVariable(_functionCall).commaSeparatedList();
templ("retVars", retVars);
templ("hasRetVars", !retVars.empty());
solAssert(retVars.empty() == returnInfo.returnTypes.empty(), "");
templ("roundUp", m_utils.roundUpFunction());
templ("abiDecode", abi.tupleDecoder(returnInfo.returnTypes, true));
templ("dynamicReturnSize", returnInfo.dynamicReturnSize);
templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
templ("noTryCall", !_functionCall.annotation().tryCall);
// If the function takes arbitrary parameters or is a bare call, copy dynamic length data in place.
// Move arguments to memory, will not update the free memory pointer (but will update the memory
@ -1356,9 +1670,15 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
// but all parameters of ecrecover are value types anyway.
encodeInPlace = false;
bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall;
solUnimplementedAssert(!encodeInPlace, "");
solUnimplementedAssert(funType.padArguments(), "");
templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
solUnimplementedAssert(encodeInPlace == !funType.padArguments(), "");
if (encodeInPlace)
{
solUnimplementedAssert(!encodeForLibraryCall, "");
templ("encodeArgs", abi.tupleEncoderPacked(argumentTypes, funType.parameterTypes()));
}
else
templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
templ("argumentString", argumentString);
// Output data will replace input data, unless we have ECRecover (then, output
@ -1401,24 +1721,9 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
templ("forwardingRevert", m_utils.forwardingRevertFunction());
solUnimplementedAssert(!returnSuccessConditionAndReturndata, "");
solUnimplementedAssert(funKind != FunctionType::Kind::RIPEMD160, "");
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
templ("dynamicReturnSize", dynamicReturnSize);
// Always use the actual return length, and not our calculated expected length, if returndatacopy is supported.
// This ensures it can catch badly formatted input from external calls.
if (haveReturndatacopy)
templ("returnSize", "returndatasize()");
else
templ("returnSize", to_string(estimatedReturnSize));
templ("reservedReturnSize", dynamicReturnSize ? "0" : to_string(estimatedReturnSize));
templ("abiDecode", abi.tupleDecoder(returnTypes, true));
templ("returns", !returnTypes.empty());
templ("retVars", IRVariable(_functionCall).commaSeparatedList());
m_code << templ.render();
}
@ -1471,14 +1776,17 @@ void IRGeneratorForStatements::declareAssign(IRVariable const& _lhs, IRVariable
else
m_code << (_declare ? "let ": "") << _lhs.part(stackItemName).name() << " := " << _rhs.part(stackItemName).name() << "\n";
else
m_code <<
(_declare ? "let ": "") <<
_lhs.commaSeparatedList() <<
" := " <<
m_context.utils().conversionFunction(_rhs.type(), _lhs.type()) <<
{
if (_lhs.type().sizeOnStack() > 0)
m_code <<
(_declare ? "let ": "") <<
_lhs.commaSeparatedList() <<
" := ";
m_code << m_context.utils().conversionFunction(_rhs.type(), _lhs.type()) <<
"(" <<
_rhs.commaSeparatedList() <<
")\n";
}
}
IRVariable IRGeneratorForStatements::zeroValue(Type const& _type, bool _splitFunctionTypes)
@ -1540,6 +1848,15 @@ string IRGeneratorForStatements::binaryOperation(
case Token::Mod:
fun = m_utils.checkedIntModFunction(*type);
break;
case Token::BitOr:
fun = "or";
break;
case Token::BitXor:
fun = "xor";
break;
case Token::BitAnd:
fun = "and";
break;
default:
break;
}
@ -1688,7 +2005,7 @@ void IRGeneratorForStatements::setLValue(Expression const& _expression, IRLValue
{
solAssert(!m_currentLValue, "");
if (_expression.annotation().lValueRequested)
if (_expression.annotation().willBeWrittenTo)
{
m_currentLValue.emplace(std::move(_lvalue));
solAssert(!_lvalue.type.dataStoredIn(DataLocation::CallData), "");
@ -1749,3 +2066,118 @@ Type const& IRGeneratorForStatements::type(Expression const& _expression)
solAssert(_expression.annotation().type, "Type of expression not set.");
return *_expression.annotation().type;
}
bool IRGeneratorForStatements::visit(TryStatement const& _tryStatement)
{
Expression const& externalCall = _tryStatement.externalCall();
externalCall.accept(*this);
m_code << "switch iszero(" << m_context.trySuccessConditionVariable(externalCall) << ")\n";
m_code << "case 0 { // success case\n";
TryCatchClause const& successClause = *_tryStatement.clauses().front();
if (successClause.parameters())
{
size_t i = 0;
for (ASTPointer<VariableDeclaration> const& varDecl: successClause.parameters()->parameters())
{
solAssert(varDecl, "");
define(m_context.addLocalVariable(*varDecl),
successClause.parameters()->parameters().size() == 1 ?
IRVariable(externalCall) :
IRVariable(externalCall).tupleComponent(i++)
);
}
}
successClause.block().accept(*this);
m_code << "}\n";
m_code << "default { // failure case\n";
handleCatch(_tryStatement);
m_code << "}\n";
return false;
}
void IRGeneratorForStatements::handleCatch(TryStatement const& _tryStatement)
{
if (_tryStatement.structuredClause())
handleCatchStructuredAndFallback(*_tryStatement.structuredClause(), _tryStatement.fallbackClause());
else if (_tryStatement.fallbackClause())
handleCatchFallback(*_tryStatement.fallbackClause());
else
rethrow();
}
void IRGeneratorForStatements::handleCatchStructuredAndFallback(
TryCatchClause const& _structured,
TryCatchClause const* _fallback
)
{
solAssert(
_structured.parameters() &&
_structured.parameters()->parameters().size() == 1 &&
_structured.parameters()->parameters().front() &&
*_structured.parameters()->parameters().front()->annotation().type == *TypeProvider::stringMemory(),
""
);
solAssert(m_context.evmVersion().supportsReturndata(), "");
// Try to decode the error message.
// If this fails, leaves 0 on the stack, otherwise the pointer to the data string.
string const dataVariable = m_context.newYulVariable();
m_code << "let " << dataVariable << " := " << m_utils.tryDecodeErrorMessageFunction() << "()\n";
m_code << "switch iszero(" << dataVariable << ") \n";
m_code << "case 0 { // decoding success\n";
if (_structured.parameters())
{
solAssert(_structured.parameters()->parameters().size() == 1, "");
IRVariable const& var = m_context.addLocalVariable(*_structured.parameters()->parameters().front());
define(var) << dataVariable << "\n";
}
_structured.accept(*this);
m_code << "}\n";
m_code << "default { // decoding failure\n";
if (_fallback)
handleCatchFallback(*_fallback);
else
rethrow();
m_code << "}\n";
}
void IRGeneratorForStatements::handleCatchFallback(TryCatchClause const& _fallback)
{
if (_fallback.parameters())
{
solAssert(m_context.evmVersion().supportsReturndata(), "");
solAssert(
_fallback.parameters()->parameters().size() == 1 &&
_fallback.parameters()->parameters().front() &&
*_fallback.parameters()->parameters().front()->annotation().type == *TypeProvider::bytesMemory(),
""
);
VariableDeclaration const& paramDecl = *_fallback.parameters()->parameters().front();
define(m_context.addLocalVariable(paramDecl)) << m_utils.extractReturndataFunction() << "()\n";
}
_fallback.accept(*this);
}
void IRGeneratorForStatements::rethrow()
{
if (m_context.evmVersion().supportsReturndata())
m_code << R"(
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
)"s;
else
m_code << "revert(0, 0) // rethrow\n"s;
}
bool IRGeneratorForStatements::visit(TryCatchClause const& _clause)
{
_clause.block().accept(*this);
return false;
}

View File

@ -24,6 +24,8 @@
#include <libsolidity/codegen/ir/IRLValue.h>
#include <libsolidity/codegen/ir/IRVariable.h>
#include <functional>
namespace solidity::frontend
{
@ -49,6 +51,9 @@ public:
/// Generates code to initialize the given local variable.
void initializeLocalVar(VariableDeclaration const& _varDecl);
/// Calculates expression's value and returns variable where it was stored
IRVariable evaluateExpression(Expression const& _expression, Type const& _to);
void endVisit(VariableDeclarationStatement const& _variableDeclaration) override;
bool visit(Conditional const& _conditional) override;
bool visit(Assignment const& _assignment) override;
@ -70,7 +75,26 @@ public:
void endVisit(Identifier const& _identifier) override;
bool visit(Literal const& _literal) override;
bool visit(TryStatement const& _tryStatement) override;
bool visit(TryCatchClause const& _tryCatchClause) override;
private:
/// Handles all catch cases of a try statement, except the success-case.
void handleCatch(TryStatement const& _tryStatement);
void handleCatchStructuredAndFallback(
TryCatchClause const& _structured,
TryCatchClause const* _fallback
);
void handleCatchFallback(TryCatchClause const& _fallback);
/// Generates code to rethrow an exception.
void rethrow();
void handleVariableReference(
VariableDeclaration const& _variable,
Expression const& _referencingExpression
);
/// Appends code to call an external function with the given arguments.
/// All involved expressions have already been visited.
void appendExternalFunctionCall(
@ -123,7 +147,7 @@ private:
/// @returns a fresh IR variable containing the value of the lvalue @a _lvalue.
IRVariable readFromLValue(IRLValue const& _lvalue);
/// Stores the given @a _lvalue in m_currentLValue, if it will be written to (lValueRequested). Otherwise
/// Stores the given @a _lvalue in m_currentLValue, if it will be written to (willBeWrittenTo). Otherwise
/// defines the expression @a _expression by reading the value from @a _lvalue.
void setLValue(Expression const& _expression, IRLValue _lvalue);
void generateLoop(

View File

@ -29,8 +29,7 @@ class Expression;
/**
* An IRVariable refers to a set of yul variables that correspond to the stack layout of a Solidity variable or expression
* of a specific S
* olidity type. If the Solidity type occupies a single stack slot, the IRVariable refers to a single yul variable.
* of a specific Solidity type. If the Solidity type occupies a single stack slot, the IRVariable refers to a single yul variable.
* Otherwise the set of yul variables it refers to is (recursively) determined by @see ``Type::stackItems()``.
* For example, an IRVariable referring to a dynamically sized calldata array will consist of two parts named
* ``offset`` and ``length``, whereas an IRVariable referring to a statically sized calldata type, a storage reference

View File

@ -304,7 +304,10 @@ void BMC::endVisit(UnaryOperation const& _op)
{
SMTEncoder::endVisit(_op);
if (_op.annotation().type->category() == Type::Category::RationalNumber)
if (
_op.annotation().type->category() == Type::Category::RationalNumber ||
_op.annotation().type->category() == Type::Category::FixedPoint
)
return;
switch (_op.getOperator())

View File

@ -31,6 +31,7 @@
using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::langutil;
using namespace solidity::frontend;
@ -130,15 +131,10 @@ bool CHC::visit(ContractDefinition const& _contract)
clearIndices(&_contract);
auto errorFunctionSort = make_shared<smt::FunctionSort>(
vector<smt::SortPointer>(),
smt::SortProvider::boolSort
);
string suffix = _contract.name() + "_" + to_string(_contract.id());
m_errorPredicate = createSymbolicBlock(errorFunctionSort, "error_" + suffix);
m_errorPredicate = createSymbolicBlock(arity0FunctionSort(), "error_" + suffix);
m_constructorSummaryPredicate = createSymbolicBlock(constructorSort(), "summary_constructor_" + suffix);
m_implicitConstructorPredicate = createSymbolicBlock(interfaceSort(), "implicit_constructor_" + suffix);
m_implicitConstructorPredicate = createSymbolicBlock(arity0FunctionSort(), "implicit_constructor_" + suffix);
auto stateExprs = currentStateVariables();
setCurrentBlock(*m_interfaces.at(m_currentContract), &stateExprs);
@ -148,15 +144,7 @@ bool CHC::visit(ContractDefinition const& _contract)
void CHC::endVisit(ContractDefinition const& _contract)
{
for (auto const& var: m_stateVariables)
{
solAssert(m_context.knownVariable(*var), "");
auto const& symbVar = m_context.variable(*var);
symbVar->resetIndex();
m_context.setZeroValue(*var);
symbVar->increaseIndex();
}
auto implicitConstructor = (*m_implicitConstructorPredicate)(initialStateVariables());
auto implicitConstructor = (*m_implicitConstructorPredicate)({});
connectBlocks(genesis(), implicitConstructor);
m_currentBlock = implicitConstructor;
m_context.addAssertion(m_error.currentValue() == 0);
@ -643,19 +631,19 @@ set<Expression const*, CHC::IdCompare> CHC::transactionAssertions(ASTNode const*
vector<VariableDeclaration const*> CHC::stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract)
{
vector<VariableDeclaration const*> stateVars;
for (auto const& contract: _contract.annotation().linearizedBaseContracts)
for (auto var: contract->stateVariables())
stateVars.push_back(var);
return stateVars;
return fold(
_contract.annotation().linearizedBaseContracts,
vector<VariableDeclaration const*>{},
[](auto&& _acc, auto _contract) { return _acc + _contract->stateVariables(); }
);
}
vector<smt::SortPointer> CHC::stateSorts(ContractDefinition const& _contract)
{
vector<smt::SortPointer> stateSorts;
for (auto const& var: stateVariablesIncludingInheritedAndPrivate(_contract))
stateSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
return stateSorts;
return applyMap(
stateVariablesIncludingInheritedAndPrivate(_contract),
[](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); }
);
}
smt::SortPointer CHC::constructorSort()
@ -682,6 +670,14 @@ smt::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
);
}
smt::SortPointer CHC::arity0FunctionSort()
{
return make_shared<smt::FunctionSort>(
vector<smt::SortPointer>(),
smt::SortProvider::boolSort
);
}
/// A function in the symbolic CFG requires:
/// - Index of failed assertion. 0 means no assertion failed.
/// - 2 sets of state variables:
@ -695,12 +691,9 @@ smt::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
/// - 1 set of output variables
smt::SortPointer CHC::sort(FunctionDefinition const& _function)
{
vector<smt::SortPointer> inputSorts;
for (auto const& var: _function.parameters())
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
vector<smt::SortPointer> outputSorts;
for (auto const& var: _function.returnParameters())
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
auto inputSorts = applyMap(_function.parameters(), smtSort);
auto outputSorts = applyMap(_function.returnParameters(), smtSort);
return make_shared<smt::FunctionSort>(
vector<smt::SortPointer>{smt::SortProvider::intSort} + m_stateSorts + inputSorts + m_stateSorts + inputSorts + outputSorts,
smt::SortProvider::boolSort
@ -715,11 +708,9 @@ smt::SortPointer CHC::sort(ASTNode const* _node)
auto fSort = dynamic_pointer_cast<smt::FunctionSort>(sort(*m_currentFunction));
solAssert(fSort, "");
vector<smt::SortPointer> varSorts;
for (auto const& var: m_currentFunction->localVariables())
varSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
return make_shared<smt::FunctionSort>(
fSort->domain + varSorts,
fSort->domain + applyMap(m_currentFunction->localVariables(), smtSort),
smt::SortProvider::boolSort
);
}
@ -729,11 +720,9 @@ smt::SortPointer CHC::summarySort(FunctionDefinition const& _function, ContractD
auto stateVariables = stateVariablesIncludingInheritedAndPrivate(_contract);
auto sorts = stateSorts(_contract);
vector<smt::SortPointer> inputSorts, outputSorts;
for (auto const& var: _function.parameters())
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
for (auto const& var: _function.returnParameters())
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
auto smtSort = [](auto _var) { return smt::smtSortAbstractFunction(*_var->type()); };
auto inputSorts = applyMap(_function.parameters(), smtSort);
auto outputSorts = applyMap(_function.returnParameters(), smtSort);
return make_shared<smt::FunctionSort>(
vector<smt::SortPointer>{smt::SortProvider::intSort} + sorts + inputSorts + sorts + outputSorts,
smt::SortProvider::boolSort
@ -769,9 +758,10 @@ void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
smt::Expression CHC::interface()
{
vector<smt::Expression> paramExprs;
for (auto const& var: m_stateVariables)
paramExprs.push_back(m_context.variable(*var)->currentValue());
auto paramExprs = applyMap(
m_stateVariables,
[this](auto _var) { return m_context.variable(*_var)->currentValue(); }
);
return (*m_interfaces.at(m_currentContract))(paramExprs);
}
@ -803,11 +793,9 @@ smt::Expression CHC::summary(FunctionDefinition const& _function)
vector<smt::Expression> args{m_error.currentValue()};
auto contract = _function.annotation().contract;
args += contract->isLibrary() ? stateVariablesAtIndex(0, *contract) : initialStateVariables();
for (auto const& var: _function.parameters())
args.push_back(m_context.variable(*var)->valueAtIndex(0));
args += applyMap(_function.parameters(), [this](auto _var) { return valueAtIndex(*_var, 0); });
args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
for (auto const& var: _function.returnParameters())
args.push_back(m_context.variable(*var)->currentValue());
args += applyMap(_function.returnParameters(), [this](auto _var) { return currentValue(*_var); });
return (*m_summaries.at(m_currentContract).at(&_function))(args);
}
@ -854,27 +842,21 @@ vector<smt::Expression> CHC::initialStateVariables()
vector<smt::Expression> CHC::stateVariablesAtIndex(int _index)
{
solAssert(m_currentContract, "");
vector<smt::Expression> exprs;
for (auto const& var: m_stateVariables)
exprs.push_back(m_context.variable(*var)->valueAtIndex(_index));
return exprs;
return applyMap(m_stateVariables, [&](auto _var) { return valueAtIndex(*_var, _index); });
}
vector<smt::Expression> CHC::stateVariablesAtIndex(int _index, ContractDefinition const& _contract)
{
vector<smt::Expression> exprs;
for (auto const& var: stateVariablesIncludingInheritedAndPrivate(_contract))
exprs.push_back(m_context.variable(*var)->valueAtIndex(_index));
return exprs;
return applyMap(
stateVariablesIncludingInheritedAndPrivate(_contract),
[&](auto _var) { return valueAtIndex(*_var, _index); }
);
}
vector<smt::Expression> CHC::currentStateVariables()
{
solAssert(m_currentContract, "");
vector<smt::Expression> exprs;
for (auto const& var: m_stateVariables)
exprs.push_back(m_context.variable(*var)->currentValue());
return exprs;
return applyMap(m_stateVariables, [this](auto _var) { return currentValue(*_var); });
}
vector<smt::Expression> CHC::currentFunctionVariables()
@ -886,9 +868,7 @@ vector<smt::Expression> CHC::currentFunctionVariables()
initInputExprs.push_back(m_context.variable(*var)->valueAtIndex(0));
mutableInputExprs.push_back(m_context.variable(*var)->currentValue());
}
vector<smt::Expression> returnExprs;
for (auto const& var: m_currentFunction->returnParameters())
returnExprs.push_back(m_context.variable(*var)->currentValue());
auto returnExprs = applyMap(m_currentFunction->returnParameters(), [this](auto _var) { return currentValue(*_var); });
return vector<smt::Expression>{m_error.currentValue()} +
initialStateVariables() +
initInputExprs +
@ -899,11 +879,10 @@ vector<smt::Expression> CHC::currentFunctionVariables()
vector<smt::Expression> CHC::currentBlockVariables()
{
vector<smt::Expression> paramExprs;
if (m_currentFunction)
for (auto const& var: m_currentFunction->localVariables())
paramExprs.push_back(m_context.variable(*var)->currentValue());
return currentFunctionVariables() + paramExprs;
return currentFunctionVariables() + applyMap(m_currentFunction->localVariables(), [this](auto _var) { return currentValue(*_var); });
return currentFunctionVariables();
}
string CHC::predicateName(ASTNode const* _node, ContractDefinition const* _contract)
@ -958,8 +937,7 @@ smt::Expression CHC::predicate(FunctionCall const& _funCall)
m_context.variable(*param)->increaseIndex();
else
createVariable(*param);
for (auto const& var: function->returnParameters())
args.push_back(m_context.variable(*var)->currentValue());
args += applyMap(function->returnParameters(), [this](auto _var) { return currentValue(*_var); });
if (contract->isLibrary())
return (*m_summaries.at(contract).at(function))(args);

View File

@ -106,6 +106,7 @@ private:
smt::SortPointer constructorSort();
smt::SortPointer interfaceSort();
static smt::SortPointer interfaceSort(ContractDefinition const& _const);
smt::SortPointer arity0FunctionSort();
smt::SortPointer sort(FunctionDefinition const& _function);
smt::SortPointer sort(ASTNode const* _block);
/// @returns the sort of a predicate that represents the summary of _function in the scope of _contract.

View File

@ -196,6 +196,16 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr)
solAssert(sortSort, "");
return m_context.mkConst(CVC4::ArrayStoreAll(cvc4Sort(*sortSort->inner), arguments[1]));
}
else if (n == "tuple_get")
{
shared_ptr<TupleSort> tupleSort = std::dynamic_pointer_cast<TupleSort>(_expr.arguments[0].sort);
solAssert(tupleSort, "");
CVC4::DatatypeType tt = m_context.mkTupleType(cvc4Sort(tupleSort->components));
CVC4::Datatype const& dt = tt.getDatatype();
size_t index = std::stoi(_expr.arguments[1].name);
CVC4::Expr s = dt[0][index].getSelector();
return m_context.mkExpr(CVC4::kind::APPLY_SELECTOR, s, arguments[0]);
}
solAssert(false, "");
}
@ -229,6 +239,11 @@ CVC4::Type CVC4Interface::cvc4Sort(Sort const& _sort)
auto const& arraySort = dynamic_cast<ArraySort const&>(_sort);
return m_context.mkArrayType(cvc4Sort(*arraySort.domain), cvc4Sort(*arraySort.range));
}
case Kind::Tuple:
{
auto const& tupleSort = dynamic_cast<TupleSort const&>(_sort);
return m_context.mkTupleType(cvc4Sort(tupleSort.components));
}
default:
break;
}

View File

@ -107,6 +107,9 @@ void SMTEncoder::endVisit(ContractDefinition const& _contract)
solAssert(m_currentContract == &_contract, "");
m_currentContract = nullptr;
if (m_callStack.empty())
m_context.popSolver();
}
void SMTEncoder::endVisit(VariableDeclaration const& _varDecl)
@ -191,7 +194,8 @@ void SMTEncoder::inlineModifierInvocation(ModifierInvocation const* _invocation,
pushCallStack({_definition, _invocation});
if (auto modifier = dynamic_cast<ModifierDefinition const*>(_definition))
{
modifier->body().accept(*this);
if (modifier->isImplemented())
modifier->body().accept(*this);
popCallStack();
}
else if (auto function = dynamic_cast<FunctionDefinition const*>(_definition))
@ -296,16 +300,22 @@ void SMTEncoder::endVisit(VariableDeclarationStatement const& _varDecl)
{
auto symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(*init));
solAssert(symbTuple, "");
auto const& components = symbTuple->components();
auto const& symbComponents = symbTuple->components();
auto tupleType = dynamic_cast<TupleType const*>(init->annotation().type);
solAssert(tupleType, "");
solAssert(tupleType->components().size() == symbTuple->components().size(), "");
auto const& components = tupleType->components();
auto const& declarations = _varDecl.declarations();
solAssert(components.size() == declarations.size(), "");
solAssert(symbComponents.size() == declarations.size(), "");
for (unsigned i = 0; i < declarations.size(); ++i)
if (
components.at(i) &&
declarations.at(i) &&
m_context.knownVariable(*declarations.at(i))
)
assignment(*declarations.at(i), components.at(i)->currentValue(declarations.at(i)->type()));
assignment(*declarations.at(i), symbTuple->component(i, components.at(i), declarations.at(i)->type()));
}
}
else if (m_context.knownVariable(*_varDecl.declarations().front()))
@ -354,7 +364,7 @@ void SMTEncoder::endVisit(Assignment const& _assignment)
{
auto const& type = _assignment.annotation().type;
vector<smt::Expression> rightArguments;
if (_assignment.rightHandSide().annotation().type->category() == Type::Category::Tuple)
if (auto const* tupleTypeRight = dynamic_cast<TupleType const*>(_assignment.rightHandSide().annotation().type))
{
auto symbTupleLeft = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_assignment.leftHandSide()));
solAssert(symbTupleLeft, "");
@ -365,17 +375,16 @@ void SMTEncoder::endVisit(Assignment const& _assignment)
auto const& rightComponents = symbTupleRight->components();
solAssert(leftComponents.size() == rightComponents.size(), "");
for (unsigned i = 0; i < leftComponents.size(); ++i)
{
auto const& left = leftComponents.at(i);
auto const& right = rightComponents.at(i);
/// Right hand side tuple component cannot be empty.
solAssert(right, "");
if (left)
rightArguments.push_back(right->currentValue(left->originalType()));
else
rightArguments.push_back(right->currentValue());
}
auto tupleTypeLeft = dynamic_cast<TupleType const*>(_assignment.leftHandSide().annotation().type);
solAssert(tupleTypeLeft, "");
solAssert(tupleTypeLeft->components().size() == leftComponents.size(), "");
auto const& typesLeft = tupleTypeLeft->components();
solAssert(tupleTypeRight->components().size() == rightComponents.size(), "");
auto const& typesRight = tupleTypeRight->components();
for (unsigned i = 0; i < rightComponents.size(); ++i)
rightArguments.push_back(symbTupleRight->component(i, typesRight.at(i), typesLeft.at(i)));
}
else
{
@ -418,17 +427,16 @@ void SMTEncoder::endVisit(TupleExpression const& _tuple)
solAssert(symbComponents.size() == tupleComponents->size(), "");
for (unsigned i = 0; i < symbComponents.size(); ++i)
{
auto sComponent = symbComponents.at(i);
auto tComponent = tupleComponents->at(i);
if (sComponent && tComponent)
if (tComponent)
{
if (auto varDecl = identifierToVariable(*tComponent))
m_context.addAssertion(sComponent->currentValue() == currentValue(*varDecl));
m_context.addAssertion(symbTuple->component(i) == currentValue(*varDecl));
else
{
if (!m_context.knownExpression(*tComponent))
createExpr(*tComponent);
m_context.addAssertion(sComponent->currentValue() == expr(*tComponent));
m_context.addAssertion(symbTuple->component(i) == expr(*tComponent));
}
}
}
@ -450,6 +458,9 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
createExpr(_op);
if (_op.annotation().type->category() == Type::Category::FixedPoint)
return;
switch (_op.getOperator())
{
case Token::Not: // !
@ -463,7 +474,7 @@ void SMTEncoder::endVisit(UnaryOperation const& _op)
{
solAssert(smt::isInteger(_op.annotation().type->category()), "");
solAssert(_op.subExpression().annotation().lValueRequested, "");
solAssert(_op.subExpression().annotation().willBeWrittenTo, "");
if (auto identifier = dynamic_cast<Identifier const*>(&_op.subExpression()))
{
auto decl = identifierToVariable(*identifier);
@ -658,7 +669,6 @@ void SMTEncoder::initFunction(FunctionDefinition const& _function)
{
solAssert(m_callStack.empty(), "");
solAssert(m_currentContract, "");
m_context.reset();
m_context.pushSolver();
m_pathConditions.clear();
pushCallStack({&_function, nullptr});
@ -700,7 +710,7 @@ void SMTEncoder::visitGasLeft(FunctionCall const& _funCall)
void SMTEncoder::endVisit(Identifier const& _identifier)
{
if (_identifier.annotation().lValueRequested)
if (_identifier.annotation().willBeWrittenTo)
{
// Will be translated as part of the node that requested the lvalue.
}
@ -807,13 +817,15 @@ void SMTEncoder::endVisit(Return const& _return)
{
auto const& symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(*_return.expression()));
solAssert(symbTuple, "");
auto const& components = symbTuple->components();
solAssert(components.size() == returnParams.size(), "");
solAssert(symbTuple->components().size() == returnParams.size(), "");
auto const* tupleType = dynamic_cast<TupleType const*>(_return.expression()->annotation().type);
solAssert(tupleType, "");
auto const& types = tupleType->components();
solAssert(types.size() == returnParams.size(), "");
for (unsigned i = 0; i < returnParams.size(); ++i)
{
solAssert(components.at(i), "");
m_context.addAssertion(components.at(i)->currentValue(returnParams.at(i)->type()) == m_context.newValue(*returnParams.at(i)));
}
m_context.addAssertion(symbTuple->component(i, types.at(i), returnParams.at(i)->type()) == m_context.newValue(*returnParams.at(i)));
}
else if (returnParams.size() == 1)
m_context.addAssertion(expr(*_return.expression(), returnParams.front()->type()) == m_context.newValue(*returnParams.front()));
@ -1676,14 +1688,10 @@ void SMTEncoder::createReturnedExpressions(FunctionCall const& _funCall)
solAssert(symbComponents.size() == returnParams.size(), "");
for (unsigned i = 0; i < symbComponents.size(); ++i)
{
auto sComponent = symbComponents.at(i);
auto param = returnParams.at(i);
solAssert(param, "");
if (sComponent)
{
solAssert(m_context.knownVariable(*param), "");
m_context.addAssertion(sComponent->currentValue() == currentValue(*param));
}
solAssert(m_context.knownVariable(*param), "");
m_context.addAssertion(symbTuple->component(i) == currentValue(*param));
}
}
else if (returnParams.size() == 1)

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